/*!
    \file generator_cpp.cpp
    \brief Fast binary encoding C++ generator implementation
    \author Ivan Shynkarenka
    \date 20.04.2018
    \copyright MIT License
*/

#include "generator_cpp.h"

namespace FBE {

void GeneratorCpp::Generate(const std::shared_ptr<Package>& package)
{
    // Generate common files
    GenerateFBE_Header(_output);
    GenerateFBE_Source(_output);

    // Generate common models files
    GenerateFBEModels_Header(_output);
    GenerateFBEModels_Inline(_output);
    GenerateFBEModels_Source(_output);
    if (Final())
    {
        GenerateFBEFinalModels_Header(_output);
        GenerateFBEFinalModels_Inline(_output);
        GenerateFBEFinalModels_Source(_output);
    }

    // Generate common protocol files
    if (Proto())
    {
        GenerateFBEProtocol_Header(_output);
        GenerateFBEProtocol_Source(_output);
    }

    // Generate common JSON files
    if (JSON())
        GenerateFBEJson_Header(_output);

    // Generate package files
    GeneratePackage_Header(package);
    GeneratePackage_Source(package);
    if (JSON())
        GeneratePackage_Json(package);

    // Generate package models files
    GeneratePackageModels_Header(package);
    GeneratePackageModels_Source(package);
    if (Final())
    {
        GeneratePackageFinalModels_Header(package);
        GeneratePackageFinalModels_Source(package);
    }

    // Generate package protocol files
    if (Proto())
    {
        GeneratePackageProtocol_Header(package, false);
        GeneratePackageProtocol_Source(package, false);
        if (Final())
        {
            GeneratePackageProtocol_Header(package, true);
            GeneratePackageProtocol_Source(package, true);
        }
    }
}

void GeneratorCpp::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(//------------------------------------------------------------------------------
// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// FBE version: _VERSION_
//------------------------------------------------------------------------------

#pragma once

#if defined(__clang__)
#pragma clang system_header
#elif defined(__GNUC__)
#pragma GCC system_header
#elif defined(_MSC_VER)
#pragma system_header
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateInline(const std::string& source)
{
    std::string code = R"CODE(//------------------------------------------------------------------------------
// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// FBE version: _VERSION_
//------------------------------------------------------------------------------
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateSource(const std::string& source)
{
    std::string code = R"CODE(//------------------------------------------------------------------------------
// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// FBE version: _VERSION_
//------------------------------------------------------------------------------
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateWarningsHeader()
{
    std::string code = R"CODE(
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4065) // C4065: switch statement contains 'default' but no 'case' labels
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateWarningsFooter()
{
    std::string code = R"CODE(
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFooter()
{
}

void GeneratorCpp::GenerateImports()
{
    std::string code = R"CODE(
#include <array>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstring>
#include <cctype>
#include <future>
#include <iomanip>
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>

#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
#include <time.h>
#include <uuid/uuid.h>
#elif defined(_WIN32) || defined(_WIN64)
#undef DELETE
#undef ERROR
#undef Yield
#undef min
#undef max
#undef uuid_t
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate logging protocol definitions
    if (Logging())
    {
        WriteLine();
        WriteLineIndent("#define LOGGING_PROTOCOL 1");
        WriteLineIndent("#include <logging/logger.h>");
    }
}

void GeneratorCpp::GenerateImportsSource()
{
    std::string code = R"CODE(
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <rpc.h>
#undef DELETE
#undef ERROR
#undef Yield
#undef min
#undef max
#undef uuid_t
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateImports(const std::string& source)
{
    WriteLine();
    WriteLineIndent("#include \"" + source + "\"");
}

void GeneratorCpp::GenerateImports(const std::shared_ptr<Package>& p)
{
    // Generate common imports
    WriteLine();
    WriteLineIndent("#include \"fbe.h\"");

    // Generate packages import
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent("#include \"" + *import + ".h\"");
    }

    // Generate domain namespace using
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");
    WriteLineIndent("using namespace FBE;");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("using namespace ::" + *import + ";");
    }
    WriteLineIndent("} // namespace " + *p->name);

    // Generate FBE namespace using
    WriteLine();
    WriteLineIndent("namespace FBE {");
    WriteLineIndent("using namespace ::" + *p->name + ";");
    WriteLineIndent("} // namespace FBE");
}

void GeneratorCpp::GenerateImportsModels(const std::shared_ptr<Package>& p, bool final)
{
    // Generate common imports
    WriteLine();
    WriteLineIndent("#include \"" + *p->name + ".h\"");

    // Generate packages import
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent("#include \"" + *import + (final ? "_final" : "") + "_models.h\"");
    }
}

void GeneratorCpp::GenerateImportsProtocol(const std::shared_ptr<Package>& p, bool final)
{
    // Generate common imports
    WriteLine();
    WriteLineIndent("#include \"" + *p->name + (final ? "_final" : "") + "_models.h\"");

    // Generate packages import
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent("#include \"" + *import + (final ? "_final" : "") + "_protocol.h\"");
    }
}

void GeneratorCpp::GenerateImportsJson()
{
    WriteLine();
    WriteLineIndent("#define RAPIDJSON_HAS_STDSTRING 1");
    WriteLineIndent("#include <rapidjson/document.h>");
    WriteLineIndent("#include <rapidjson/prettywriter.h>");
}

void GeneratorCpp::GenerateImportsJson(const std::shared_ptr<Package>& p)
{
    // Generate common imports
    WriteLine();
    WriteLineIndent("#include \"fbe_json.h\"");

    // Generate common imports
    WriteLine();
    WriteLineIndent("#include \"" + *p->name + ".h\"");

    // Generate packages import
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
            WriteLineIndent("#include \"" + *import + "_json.h\"");
    }
}

void GeneratorCpp::GenerateBufferWrapper_Header()
{
    std::string code = R"CODE(
//! Bytes buffer type
/*!
    Represents bytes buffer which is a lightweight wrapper around std::vector<uint8_t>
    with similar interface.
*/
class buffer_t
{
public:
    typedef std::vector<uint8_t>::iterator iterator;
    typedef std::vector<uint8_t>::const_iterator const_iterator;
    typedef std::vector<uint8_t>::reverse_iterator reverse_iterator;
    typedef std::vector<uint8_t>::const_reverse_iterator const_reverse_iterator;

    buffer_t() = default;
    buffer_t(size_t capacity) { reserve(capacity); }
    buffer_t(const std::string& str) { assign(str); }
    buffer_t(size_t size, uint8_t value) { assign(size, value); }
    buffer_t(const uint8_t* data, size_t size) { assign(data, size); }
    buffer_t(const std::vector<uint8_t>& other) : _data(other) {}
    buffer_t(std::vector<uint8_t>&& other) : _data(std::move(other)) {}
    buffer_t(const buffer_t& other) = default;
    buffer_t(buffer_t&& other) = default;
    ~buffer_t() = default;

    buffer_t& operator=(const std::string& str) { assign(str); return *this; }
    buffer_t& operator=(const std::vector<uint8_t>& other) { _data = other; return *this; }
    buffer_t& operator=(std::vector<uint8_t>&& other) { _data = std::move(other); return *this; }
    buffer_t& operator=(const buffer_t& other) = default;
    buffer_t& operator=(buffer_t&& other) = default;

    uint8_t& operator[](size_t index) { return _data[index]; }
    const uint8_t& operator[](size_t index) const { return _data[index]; }

    bool empty() const { return _data.empty(); }
    size_t capacity() const { return _data.capacity(); }
    size_t size() const { return _data.size(); }
    size_t max_size() const { return _data.max_size(); }

    std::vector<uint8_t>& buffer() noexcept { return _data; }
    const std::vector<uint8_t>& buffer() const noexcept { return _data; }
    uint8_t* data() noexcept { return _data.data(); }
    const uint8_t* data() const noexcept { return _data.data(); }
    uint8_t& at(size_t index) { return _data.at(index); }
    const uint8_t& at(size_t index) const { return _data.at(index); }
    uint8_t& front() { return _data.front(); }
    const uint8_t& front() const { return _data.front(); }
    uint8_t& back() { return _data.back(); }
    const uint8_t& back() const { return _data.back(); }

    void reserve(size_t capacity) { _data.reserve(capacity); }
    void resize(size_t size, uint8_t value = 0) { _data.resize(size, value); }
    void shrink_to_fit() { _data.shrink_to_fit(); }

    void assign(const std::string& str) { assign((const uint8_t*)str.c_str(), str.size()); }
    void assign(const std::vector<uint8_t>& vec) { assign(vec.begin(), vec.end()); }
    void assign(size_t size, uint8_t value) { _data.assign(size, value); }
    void assign(const uint8_t* data, size_t size) { _data.assign(data, data + size); }
    template <class InputIterator>
    void assign(InputIterator first, InputIterator last) { _data.assign(first, last); }
    iterator insert(const_iterator position, uint8_t value) { return _data.insert(position, value); }
    iterator insert(const_iterator position, const std::string& str) { return insert(position, (const uint8_t*)str.c_str(), str.size()); }
    iterator insert(const_iterator position, const std::vector<uint8_t>& vec) { return insert(position, vec.begin(), vec.end()); }
    iterator insert(const_iterator position, size_t size, uint8_t value) { return _data.insert(position, size, value); }
    iterator insert(const_iterator position, const uint8_t* data, size_t size) { return _data.insert(position, data, data + size); }
    template <class InputIterator>
    iterator insert(const_iterator position, InputIterator first, InputIterator last) { return _data.insert(position, first, last); }
    iterator erase(const_iterator position) { return _data.erase(position); }
    iterator erase(const_iterator first, const_iterator last) { return _data.erase(first, last); }
    void clear() noexcept { _data.clear(); }

    void push_back(uint8_t value) { _data.push_back(value); }
    void pop_back() { _data.pop_back(); }

    template <class... Args>
    iterator emplace(const_iterator position, Args&&... args) { return _data.emplace(position, args...); }
    template <class... Args>
    void emplace_back(Args&&... args) { _data.emplace_back(args...); }

    iterator begin() noexcept { return _data.begin(); }
    const_iterator begin() const noexcept { return _data.begin(); }
    const_iterator cbegin() const noexcept { return _data.cbegin(); }
    reverse_iterator rbegin() noexcept { return _data.rbegin(); }
    const_reverse_iterator rbegin() const noexcept { return _data.rbegin(); }
    const_reverse_iterator crbegin() const noexcept { return _data.crbegin(); }
    iterator end() noexcept { return _data.end(); }
    const_iterator end() const noexcept { return _data.end(); }
    const_iterator cend() const noexcept { return _data.cend(); }
    reverse_iterator rend() noexcept { return _data.rend(); }
    const_reverse_iterator rend() const noexcept { return _data.rend(); }
    const_reverse_iterator crend() const noexcept { return _data.crend(); }

    //! Get the string equivalent from the bytes buffer
    std::string string() const { return std::string(_data.begin(), _data.end()); }

    //! Encode the Base64 string from the bytes buffer
    std::string base64encode() const;
    //! Decode the bytes buffer from the Base64 string
    static buffer_t base64decode(const std::string& str);

    //! Swap two instances
    void swap(buffer_t& value) noexcept
    { using std::swap; swap(_data, value._data); }
    friend void swap(buffer_t& value1, buffer_t& value2) noexcept
    { value1.swap(value2); }

private:
    std::vector<uint8_t> _data;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateBufferWrapper_Source()
{
    std::string code = R"CODE(
std::string buffer_t::base64encode() const
{
    const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string result;

    int val = 0;
    int valb = -6;
    for (auto c : _data)
    {
        val = (val << 8) + c;
        valb += 8;
        while (valb >= 0)
        {
            result.push_back(base64[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }

    if (valb > -6)
        result.push_back(base64[((val << 8) >> (valb + 8)) & 0x3F]);

    while (result.size() % 4)
        result.push_back('=');

    return result;
}

buffer_t buffer_t::base64decode(const std::string& str)
{
    const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    buffer_t result;

    std::vector<int> pattern(256, -1);
    for (int i = 0; i < 64; ++i)
        pattern[base64[i]] = i;

    int val = 0;
    int valb = -8;
    for (auto c : str)
    {
        if (pattern[c] == -1)
            break;

        val = (val << 6) + pattern[c];
        valb += 6;

        if (valb >= 0)
        {
            result.push_back((uint8_t)((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return result;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateDecimalWrapper_Header()
{
    std::string code = R"CODE(
//! Decimal type
/*!
    Represents decimal type using double and provides basic arithmetic operations.
*/
class decimal_t
{
public:
    decimal_t() noexcept { _value = 0.0; }
    decimal_t(int8_t value) noexcept { _value = (double)value; }
    decimal_t(uint8_t value) noexcept { _value = (double)value; }
    decimal_t(int16_t value) noexcept { _value = (double)value; }
    decimal_t(uint16_t value) noexcept { _value = (double)value; }
    decimal_t(int32_t value) noexcept { _value = (double)value; }
    decimal_t(uint32_t value) noexcept { _value = (double)value; }
    decimal_t(int64_t value) noexcept { _value = (double)value; }
    decimal_t(uint64_t value) noexcept { _value = (double)value; }
    decimal_t(float value) noexcept { _value = (double)value; }
    decimal_t(double value) noexcept { _value = value; }
    template <typename T>
    explicit decimal_t(const T& value) noexcept { _value = (double)value; }
    decimal_t(const decimal_t& value) noexcept = default;
    decimal_t(decimal_t&& value) noexcept = default;
    ~decimal_t() noexcept = default;

    template <typename T>
    decimal_t& operator=(const T& value) noexcept { _value = (double)value; return *this; }
    decimal_t& operator=(const decimal_t& value) noexcept = default;
    decimal_t& operator=(decimal_t&& value) noexcept = default;

    // Arithmetic operators
    decimal_t operator+() const noexcept { return decimal_t(_value); }
    decimal_t operator-() const noexcept { return decimal_t(-_value); }

    decimal_t& operator++() noexcept { return *this += 1; }
    decimal_t operator++(int) noexcept { decimal_t temp(*this); ++*this; return temp; }
    decimal_t& operator--() noexcept { return *this -= 1; }
    decimal_t operator--(int) noexcept { decimal_t temp(*this); --*this; return temp; }

    decimal_t& operator+=(const decimal_t& value) noexcept { return *this = *this + value; }
    decimal_t& operator-=(const decimal_t& value) noexcept { return *this = *this - value; }
    decimal_t& operator*=(const decimal_t& value) noexcept { return *this = *this * value; }
    decimal_t& operator/=(const decimal_t& value) { return *this = *this / value; }

    template <typename T>
    decimal_t& operator+=(const T& value) noexcept { return *this = *this + decimal_t(value); }
    template <typename T>
    decimal_t& operator-=(const T& value) noexcept { return *this = *this - decimal_t(value); }
    template <typename T>
    decimal_t& operator*=(const T& value) noexcept { return *this = *this * decimal_t(value); }
    template <typename T>
    decimal_t& operator/=(const T& value) { return *this = *this / decimal_t(value); }

    template <typename T>
    friend T& operator+=(T& value1, const decimal_t& value2) noexcept { return value1 = (T)(decimal_t(value1) + value2); }
    template <typename T>
    friend T& operator-=(T& value1, const decimal_t& value2) noexcept { return value1 = (T)(decimal_t(value1) - value2); }
    template <typename T>
    friend T& operator*=(T& value1, const decimal_t& value2) noexcept { return value1 = (T)(decimal_t(value1) * value2); }
    template <typename T>
    friend T& operator/=(T& value1, const decimal_t& value2) { return value1 = (T)(decimal_t(value1) / value2); }

    template <typename T>
    friend decimal_t operator+(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) + value2; }
    template <typename T>
    friend decimal_t operator+(const decimal_t& value1, const T& value2) noexcept { return value1 + decimal_t(value2); }
    friend decimal_t operator+(const decimal_t& value1, const decimal_t& value2) noexcept { return decimal_t(value1._value + value2._value); }

    template <typename T>
    friend decimal_t operator-(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) - value2; }
    template <typename T>
    friend decimal_t operator-(const decimal_t& value1, const T& value2) noexcept { return value1 - decimal_t(value2); }
    friend decimal_t operator-(const decimal_t& value1, const decimal_t& value2) noexcept { return decimal_t(value1._value - value2._value); }

    template <typename T>
    friend decimal_t operator*(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) * value2; }
    template <typename T>
    friend decimal_t operator*(const decimal_t& value1, const T& value2) noexcept { return value1 * decimal_t(value2); }
    friend decimal_t operator*(const decimal_t& value1, const decimal_t& value2) noexcept { return decimal_t(value1._value * value2._value); }

    template <typename T>
    friend decimal_t operator/(const T& value1, const decimal_t& value2) { return decimal_t(value1) / value2; }
    template <typename T>
    friend decimal_t operator/(const decimal_t& value1, const T& value2) { return value1 / decimal_t(value2); }
    friend decimal_t operator/(const decimal_t& value1, const decimal_t& value2) { return decimal_t(value1._value / value2._value); }

    // Comparison operators
    template <typename T>
    friend bool operator==(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) == value2; }
    template <typename T>
    friend bool operator==(const decimal_t& value1, const T& value2) noexcept { return value1 == decimal_t(value2); }
    friend bool operator==(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value == value2._value; }

    template <typename T>
    friend bool operator!=(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) != value2; }
    template <typename T>
    friend bool operator!=(const decimal_t& value1, const T& value2) noexcept { return value1 != decimal_t(value2); }
    friend bool operator!=(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value != value2._value; }

    template <typename T>
    friend bool operator<(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) < value2; }
    template <typename T>
    friend bool operator<(const decimal_t& value1, const T& value2) noexcept { return value1 < decimal_t(value2); }
    friend bool operator<(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value < value2._value; }

    template <typename T>
    friend bool operator>(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) > value2; }
    template <typename T>
    friend bool operator>(const decimal_t& value1, const T& value2) noexcept { return value1 > decimal_t(value2); }
    friend bool operator>(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value > value2._value; }

    template <typename T>
    friend bool operator<=(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) <= value2; }
    template <typename T>
    friend bool operator<=(const decimal_t& value1, const T& value2) noexcept { return value1 <= decimal_t(value2); }
    friend bool operator<=(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value <= value2._value; }

    template <typename T>
    friend bool operator>=(const T& value1, const decimal_t& value2) noexcept { return decimal_t(value1) >= value2; }
    template <typename T>
    friend bool operator>=(const decimal_t& value1, const T& value2) noexcept { return value1 >= decimal_t(value2); }
    friend bool operator>=(const decimal_t& value1, const decimal_t& value2) noexcept { return value1._value >= value2._value; }

    // Type cast
    operator bool() const noexcept { return (_value != 0.0); }
    operator uint8_t() const noexcept { return (uint8_t)_value; }
    operator uint16_t() const noexcept { return (uint16_t)_value; }
    operator uint32_t() const noexcept { return (uint32_t)_value; }
    operator uint64_t() const noexcept { return (uint64_t)_value; }
    operator float() const noexcept { return (float)_value; }
    operator double() const noexcept { return (double)_value; }

    //! Get string from the current decimal value
    std::string string() const { return std::to_string(_value); }

    //! Input instance from the given input stream
    friend std::istream& operator>>(std::istream& is, decimal_t& value)
    { is >> value._value; return is; }
    //! Output instance into the given output stream
    friend std::ostream& operator<<(std::ostream& os, const decimal_t& value)
    { os << value.string(); return os; }

#if defined(LOGGING_PROTOCOL)
    //! Store logging format
    friend CppLogging::Record& operator<<(CppLogging::Record& record, const decimal_t& value)
    { return record.StoreCustom(value._value); }
#endif

    //! Swap two instances
    void swap(decimal_t& value) noexcept
    { using std::swap; swap(_value, value._value); }
    friend void swap(decimal_t& value1, decimal_t& value2) noexcept
    { value1.swap(value2); }

private:
    double _value;
};

} // namespace FBE

#if defined(FMT_VERSION) && (FMT_VERSION >= 90000)
template <>
struct fmt::formatter<FBE::decimal_t> : formatter<std::string_view>
{
    template <typename FormatContext>
    auto format(const FBE::decimal_t& value, FormatContext& ctx) const
    {
        return formatter<string_view>::format((double)value, ctx);
    }
};
#endif

template <>
struct std::hash<FBE::decimal_t>
{
    typedef FBE::decimal_t argument_type;
    typedef size_t result_type;

    result_type operator() (const argument_type& value) const
    {
        result_type result = 17;
        result = result * 31 + std::hash<double>()((double)value);
        return result;
    }
};

namespace FBE {
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFlagsWrapper_Header()
{
    std::string code = R"CODE(
// Register a new enum-based flags macro
#define FBE_ENUM_FLAGS(type)\
inline FBE::Flags<type> operator|(type f1, type f2) noexcept { return FBE::Flags<type>(f1) | FBE::Flags<type>(f2); }\
inline FBE::Flags<type> operator&(type f1, type f2) noexcept { return FBE::Flags<type>(f1) & FBE::Flags<type>(f2); }\
inline FBE::Flags<type> operator^(type f1, type f2) noexcept { return FBE::Flags<type>(f1) ^ FBE::Flags<type>(f2); }

// Enum-based flags
template <typename TEnum>
class Flags
{
    // Enum underlying type
    typedef typename std::make_unsigned<typename std::underlying_type<TEnum>::type>::type type;

public:
    Flags() noexcept : _value(0) {}
    explicit Flags(type value) noexcept : _value(value) {}
    explicit Flags(TEnum value) noexcept : _value((type)value) {}
    Flags(const Flags&) noexcept = default;
    Flags(Flags&&) noexcept = default;
    ~Flags() noexcept = default;

    Flags& operator=(type value) noexcept
    { _value = value; return *this; }
    Flags& operator=(TEnum value) noexcept
    { _value = (type)value; return *this; }
    Flags& operator=(const Flags&) noexcept = default;
    Flags& operator=(Flags&&) noexcept = default;

    // Is any flag set?
    explicit operator bool() const noexcept { return isset(); }

    // Is no flag set?
    bool operator!() const noexcept { return !isset(); }

    // Reverse all flags
    Flags operator~() const noexcept { return Flags(~_value); }

    // Flags logical assign operators
    Flags& operator&=(const Flags& flags) noexcept
    { _value &= flags._value; return *this; }
    Flags& operator|=(const Flags& flags) noexcept
    { _value |= flags._value; return *this; }
    Flags& operator^=(const Flags& flags) noexcept
    { _value ^= flags._value; return *this; }

    // Flags logical friend operators
    friend Flags operator&(const Flags& flags1, const Flags& flags2) noexcept
    { return Flags(flags1._value & flags2._value); }
    friend Flags operator|(const Flags& flags1, const Flags& flags2) noexcept
    { return Flags(flags1._value | flags2._value); }
    friend Flags operator^(const Flags& flags1, const Flags& flags2) noexcept
    { return Flags(flags1._value ^ flags2._value); }

    // Flags comparison
    friend bool operator==(const Flags& flags1, const Flags& flags2) noexcept
    { return flags1._value == flags2._value; }
    friend bool operator!=(const Flags& flags1, const Flags& flags2) noexcept
    { return flags1._value != flags2._value; }

    // Convert to the enum value
    operator TEnum() const noexcept { return (TEnum)_value; }

    //! Is any flag set?
    bool isset() const noexcept { return (_value != 0); }
    //! Is the given flag set?
    bool isset(type value) const noexcept { return (_value & value) != 0; }
    //! Is the given flag set?
    bool isset(TEnum value) const noexcept { return (_value & (type)value) != 0; }

    // Get the enum value
    TEnum value() const noexcept { return (TEnum)_value; }
    // Get the underlying enum value
    type underlying() const noexcept { return _value; }
    // Get the bitset value
    std::bitset<sizeof(type) * 8> bitset() const noexcept { return {_value}; }

    // Swap two instances
    void swap(Flags& flags) noexcept { using std::swap; swap(_value, flags._value); }
    template <typename UEnum>
    friend void swap(Flags<UEnum>& flags1, Flags<UEnum>& flags2) noexcept;

private:
    type _value;
};

template <typename TEnum>
inline void swap(Flags<TEnum>& flags1, Flags<TEnum>& flags2) noexcept
{
    flags1.swap(flags2);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateTimeWrapper_Header()
{
    std::string code = R"CODE(
// Get Epoch timestamp
inline uint64_t epoch() { return 0ull; }
// Get UTC timestamp
uint64_t utc();
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateTimeWrapper_Source()
{
    std::string code = R"CODE(
uint64_t utc()
{
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    struct timespec timestamp;
    if (clock_gettime(CLOCK_REALTIME, &timestamp) != 0)
        throw std::runtime_error("Cannot get value of CLOCK_REALTIME timer!");
    return (timestamp.tv_sec * 1000000000) + timestamp.tv_nsec;
#elif defined(_WIN32) || defined(_WIN64)
    FILETIME ft;
    GetSystemTimePreciseAsFileTime(&ft);

    ULARGE_INTEGER result;
    result.LowPart = ft.dwLowDateTime;
    result.HighPart = ft.dwHighDateTime;
    return (result.QuadPart - 116444736000000000ull) * 100;
#endif
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateUUIDWrapper_Header()
{
    std::string code = R"CODE(
//! Universally unique identifier (UUID)
/*!
    A universally unique identifier (UUID) is an identifier standard used
    in software construction. This implementation generates the following
    UUID types:
    - Nil UUID0 (all bits set to zero)
    - Sequential UUID1 (time based version)
    - Random UUID4 (randomly or pseudo-randomly generated version)

    A UUID is simply a 128-bit value: "123e4567-e89b-12d3-a456-426655440000"

    https://en.wikipedia.org/wiki/Universally_unique_identifier
    https://www.ietf.org/rfc/rfc4122.txt
*/
class uuid_t
{
public:
    //! Default constructor
    uuid_t() : _data() { _data.fill(0); }
    //! Initialize UUID with a given string
    /*!
        \param uuid - UUID string
    */
    explicit uuid_t(const std::string& uuid);
    //! Initialize UUID with a given 16 bytes data buffer
    /*!
        \param data - UUID 16 bytes data buffer
    */
    explicit uuid_t(const std::array<uint8_t, 16>& data) : _data(data) {}
    uuid_t(const uuid_t&) = default;
    uuid_t(uuid_t&&) noexcept = default;
    ~uuid_t() = default;

    uuid_t& operator=(const std::string& uuid)
    { _data = uuid_t(uuid).data(); return *this; }
    uuid_t& operator=(const std::array<uint8_t, 16>& data)
    { _data = data; return *this; }
    uuid_t& operator=(const uuid_t&) = default;
    uuid_t& operator=(uuid_t&&) noexcept = default;

    // UUID comparison
    friend bool operator==(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data == uuid2._data; }
    friend bool operator!=(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data != uuid2._data; }
    friend bool operator<(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data < uuid2._data; }
    friend bool operator>(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data > uuid2._data; }
    friend bool operator<=(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data <= uuid2._data; }
    friend bool operator>=(const uuid_t& uuid1, const uuid_t& uuid2)
    { return uuid1._data >= uuid2._data; }

    //! Check if the UUID is nil UUID0 (all bits set to zero)
    explicit operator bool() const noexcept { return *this != nil(); }

    //! Get the UUID data buffer
    std::array<uint8_t, 16>& data() noexcept { return _data; }
    //! Get the UUID data buffer
    const std::array<uint8_t, 16>& data() const noexcept { return _data; }

    //! Get string from the current UUID in format "00000000-0000-0000-0000-000000000000"
    std::string string() const;

    //! Generate nil UUID0 (all bits set to zero)
    static uuid_t nil() { return uuid_t(); }
    //! Generate sequential UUID1 (time based version)
    static uuid_t sequential();
    //! Generate random UUID4 (randomly or pseudo-randomly generated version)
    static uuid_t random();

    //! Output instance into the given output stream
    friend std::ostream& operator<<(std::ostream& os, const uuid_t& uuid)
    { os << uuid.string(); return os; }

#if defined(LOGGING_PROTOCOL)
    //! Store logging format
    friend CppLogging::Record& operator<<(CppLogging::Record& record, const uuid_t& uuid);
#endif

    //! Swap two instances
    void swap(uuid_t& uuid) noexcept
    { using std::swap; swap(_data, uuid._data); }
    friend void swap(uuid_t& uuid1, uuid_t& uuid2) noexcept
    { uuid1.swap(uuid2); }

private:
    std::array<uint8_t, 16> _data;
};

} // namespace FBE

#if defined(FMT_VERSION) && (FMT_VERSION >= 90000)
template <>
struct fmt::formatter<FBE::uuid_t> : formatter<std::string_view>
{
    template <typename FormatContext>
    auto format(const FBE::uuid_t& value, FormatContext& ctx) const
    {
        return formatter<string_view>::format(value.string(), ctx);
    }
};
#endif

template <>
struct std::hash<FBE::uuid_t>
{
    typedef FBE::uuid_t argument_type;
    typedef size_t result_type;

    result_type operator() (const argument_type& value) const
    {
        result_type result = 17;
        std::hash<uint8_t> hasher;
        for (size_t i = 0; i < value.data().size(); ++i)
            result = result * 31 + hasher(value.data()[i]);
        return result;
    }
};

namespace FBE {
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateUUIDWrapper_Source()
{
    std::string code = R"CODE(
uint8_t unhex(char ch)
{
    if ((ch >= '0') && (ch <= '9'))
        return ch - '0';
    else if ((ch >= 'a') && (ch <= 'f'))
        return 10 + ch - 'a';
    else if ((ch >= 'A') && (ch <= 'F'))
        return 10 + ch - 'A';
    else
        return 255;
}

uuid_t::uuid_t(const std::string& uuid)
{
    char v1 = 0;
    char v2 = 0;
    bool pack = false;
    size_t index = 0;

    // Parse UUID string
    for (auto ch : uuid)
    {
        if ((ch == '-') || (ch == '{') || (ch == '}'))
            continue;

        if (pack)
        {
            v2 = ch;
            pack = false;
            uint8_t ui1 = unhex(v1);
            uint8_t ui2 = unhex(v2);
            if ((ui1 > 15) || (ui2 > 15))
                throw std::invalid_argument("Invalid UUID string: " + uuid);
            _data[index++] = ui1 * 16 + ui2;
            if (index >= 16)
                break;
        }
        else
        {
            v1 = ch;
            pack = true;
        }
    }

    // Fill remaining data with zeros
    for (; index < 16; ++index)
        _data[index++] = 0;
}

std::string uuid_t::string() const
{
    const char* digits = "0123456789abcdef";

    std::string result(36, '0');

    int index = 0;
    for (auto value : _data)
    {
        result[index++] = digits[(value >> 4) & 0x0F];
        result[index++] = digits[(value >> 0) & 0x0F];
        if ((index == 8) || (index == 13) || (index == 18) || (index == 23))
            result[index++] = '-';
    }

    return result;
}

uuid_t uuid_t::sequential()
{
    uuid_t result;
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    ::uuid_t uuid;
    uuid_generate_time(uuid);
    result._data[0] = uuid[0];
    result._data[1] = uuid[1];
    result._data[2] = uuid[2];
    result._data[3] = uuid[3];
    result._data[4] = uuid[4];
    result._data[5] = uuid[5];
    result._data[6] = uuid[6];
    result._data[7] = uuid[7];
    result._data[8] = uuid[8];
    result._data[9] = uuid[9];
    result._data[10] = uuid[10];
    result._data[11] = uuid[11];
    result._data[12] = uuid[12];
    result._data[13] = uuid[13];
    result._data[14] = uuid[14];
    result._data[15] = uuid[15];
#elif defined(_WIN32) || defined(_WIN64)
    ::UUID uuid;
    if (UuidCreateSequential(&uuid) != RPC_S_OK)
        throw std::runtime_error("Cannot generate sequential UUID!");

    result._data[0] = (uuid.Data1 >> 24) & 0xFF;
    result._data[1] = (uuid.Data1 >> 16) & 0xFF;
    result._data[2] = (uuid.Data1 >>  8) & 0xFF;
    result._data[3] = (uuid.Data1 >>  0) & 0xFF;
    result._data[4] = (uuid.Data2 >>  8) & 0xFF;
    result._data[5] = (uuid.Data2 >>  0) & 0xFF;

    result._data[6] = (uuid.Data3 >>  8) & 0xFF;
    result._data[7] = (uuid.Data3 >>  0) & 0xFF;

    result._data[8] = uuid.Data4[0];
    result._data[9] = uuid.Data4[1];

    result._data[10] = uuid.Data4[2];
    result._data[11] = uuid.Data4[3];
    result._data[12] = uuid.Data4[4];
    result._data[13] = uuid.Data4[5];
    result._data[14] = uuid.Data4[6];
    result._data[15] = uuid.Data4[7];
#endif
    return result;
}

uuid_t uuid_t::random()
{
    uuid_t result;
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    ::uuid_t uuid;
    uuid_generate_random(uuid);
    result._data[0] = uuid[0];
    result._data[1] = uuid[1];
    result._data[2] = uuid[2];
    result._data[3] = uuid[3];
    result._data[4] = uuid[4];
    result._data[5] = uuid[5];
    result._data[6] = uuid[6];
    result._data[7] = uuid[7];
    result._data[8] = uuid[8];
    result._data[9] = uuid[9];
    result._data[10] = uuid[10];
    result._data[11] = uuid[11];
    result._data[12] = uuid[12];
    result._data[13] = uuid[13];
    result._data[14] = uuid[14];
    result._data[15] = uuid[15];
#elif defined(_WIN32) || defined(_WIN64)
    ::UUID uuid;
    if (UuidCreate(&uuid) != RPC_S_OK)
        throw std::runtime_error("Cannot generate random UUID!");

    result._data[0] = (uuid.Data1 >> 24) & 0xFF;
    result._data[1] = (uuid.Data1 >> 16) & 0xFF;
    result._data[2] = (uuid.Data1 >>  8) & 0xFF;
    result._data[3] = (uuid.Data1 >>  0) & 0xFF;
    result._data[4] = (uuid.Data2 >>  8) & 0xFF;
    result._data[5] = (uuid.Data2 >>  0) & 0xFF;

    result._data[6] = (uuid.Data3 >>  8) & 0xFF;
    result._data[7] = (uuid.Data3 >>  0) & 0xFF;

    result._data[8] = uuid.Data4[0];
    result._data[9] = uuid.Data4[1];

    result._data[10] = uuid.Data4[2];
    result._data[11] = uuid.Data4[3];
    result._data[12] = uuid.Data4[4];
    result._data[13] = uuid.Data4[5];
    result._data[14] = uuid.Data4[6];
    result._data[15] = uuid.Data4[7];
#endif
    return result;
}

#if defined(LOGGING_PROTOCOL)
CppLogging::Record& operator<<(CppLogging::Record& record, const uuid_t& uuid)
{
    const char* digits = "0123456789abcdef";

    std::array<char, 36> result;

    int index = 0;
    for (auto value : uuid.data())
    {
        result[index++] = digits[(value >> 4) & 0x0F];
        result[index++] = digits[(value >> 0) & 0x0F];
        if ((index == 8) || (index == 13) || (index == 18) || (index == 23))
            result[index++] = '-';
    }

    return record.StoreCustom(std::string_view(result.data(), result.size()));
}
#endif
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEBuffer_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding buffer based on the dynamic byte buffer
class FBEBuffer
{
public:
    FBEBuffer() : _data(nullptr), _capacity(0), _size(0), _offset(0) {}
    // Initialize the read buffer with the given byte buffer and offset
    explicit FBEBuffer(const void* data, size_t size, size_t offset = 0) { attach(data, size, offset); }
    // Initialize the read buffer with the given byte vector and offset
    explicit FBEBuffer(const std::vector<uint8_t>& buffer, size_t offset = 0) { attach(buffer, offset); }
    // Initialize the read buffer with another buffer and offset
    explicit FBEBuffer(const FBEBuffer& buffer, size_t offset = 0) { attach(buffer.data(), buffer.size(), offset); }
    // Initialize the write buffer with the given capacity
    explicit FBEBuffer(size_t capacity) : FBEBuffer() { reserve(capacity); }
    FBEBuffer(const FBEBuffer&) = delete;
    FBEBuffer(FBEBuffer&&) noexcept = delete;
    ~FBEBuffer() { if (_capacity > 0) std::free(_data); }

    FBEBuffer& operator=(const FBEBuffer&) = delete;
    FBEBuffer& operator=(FBEBuffer&&) noexcept = delete;

    bool empty() const noexcept { return (_data == nullptr) || (_size == 0); }
    const uint8_t* data() const noexcept { return _data; }
    uint8_t* data() noexcept { return _data; }
    size_t capacity() const noexcept { return _capacity; }
    size_t size() const noexcept { return _size; }
    size_t offset() const noexcept { return _offset; }

    // Attach the given buffer with a given offset to the current read buffer
    void attach(const void* data, size_t size, size_t offset = 0);
    // Attach the given byte vector with a given offset to the current read buffer
    void attach(const std::vector<uint8_t>& buffer, size_t offset = 0);

    // Clone the given buffer with a given offset to the current buffer
    void clone(const void* data, size_t size, size_t offset = 0);
    // Clone the given vector with a given offset to the current buffer
    void clone(const std::vector<uint8_t>& buffer, size_t offset = 0);

    // Allocate memory in the current write buffer and return offset to the allocated memory block
    size_t allocate(size_t size);
    // Remove some memory of the given size from the current write buffer
    void remove(size_t offset, size_t size);
    // Reserve memory of the given capacity in the current write buffer
    void reserve(size_t capacity);
    // Resize the current write buffer
    void resize(size_t size);
    // Reset the current write buffer and its offset
    void reset();

    // Shift the current write buffer offset
    void shift(size_t offset) { _offset += offset; }
    // Unshift the current write buffer offset
    void unshift(size_t offset) { _offset -= offset; }

private:
    uint8_t* _data;
    size_t _capacity;
    size_t _size;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEBuffer_Source()
{
    std::string code = R"CODE(
void FBEBuffer::attach(const void* data, size_t size, size_t offset)
{
    assert((data != nullptr) && "Invalid buffer!");
    if (data == nullptr)
        throw std::invalid_argument("Invalid buffer!");
    assert((size > 0) && "Invalid size!");
    if (size == 0)
        throw std::invalid_argument("Invalid size!");
    assert((offset <= size) && "Invalid offset!");
    if (offset > size)
        throw std::invalid_argument("Invalid offset!");

    _data = (uint8_t*)data;
    _capacity = 0;
    _size = size;
    _offset = offset;
}

void FBEBuffer::attach(const std::vector<uint8_t>& buffer, size_t offset)
{
    assert((buffer.data() != nullptr) && "Invalid buffer!");
    if (buffer.data() == nullptr)
        throw std::invalid_argument("Invalid buffer!");
    assert((buffer.size() > 0) && "Invalid size!");
    if (buffer.size() == 0)
        throw std::invalid_argument("Invalid size!");
    assert((offset <= buffer.size()) && "Invalid offset!");
    if (offset > buffer.size())
        throw std::invalid_argument("Invalid offset!");

    _data = (uint8_t*)buffer.data();
    _capacity = 0;
    _size = buffer.size();
    _offset = offset;
}

void FBEBuffer::clone(const void* data, size_t size, size_t offset)
{
    assert((offset <= size) && "Invalid offset!");
    if (offset > size)
        throw std::invalid_argument("Invalid offset!");

    reserve(size);
    std::memcpy(_data, data, size);
    _capacity = size;
    _size = size;
    _offset = offset;
}

void FBEBuffer::clone(const std::vector<uint8_t>& buffer, size_t offset)
{
    assert((offset <= buffer.size()) && "Invalid offset!");
    if (offset > buffer.size())
        throw std::invalid_argument("Invalid offset!");

    size_t size = buffer.size();

    reserve(size);
    std::memcpy(_data, buffer.data(), size);
    _capacity = size;
    _size = size;
    _offset = offset;
}

size_t FBEBuffer::allocate(size_t size)
{
    size_t offset = _size;

    // Calculate a new buffer size
    size_t total = _size + size;

    if (total <= _capacity)
    {
        _size = total;
        return offset;
    }

    _capacity = std::max(total, 2 * _capacity);
    uint8_t* data = (uint8_t*)std::malloc(_capacity);
    std::memcpy(data, _data, _size);
    std::free(_data);
    _data = data;
    _size = total;
    return offset;
}

void FBEBuffer::remove(size_t offset, size_t size)
{
    assert(((offset + size) <= _size) && "Invalid offset & size!");
    if ((offset + size) > _size)
        throw std::invalid_argument("Invalid offset & size!");

    std::memcpy(_data + offset, _data + offset + size, _size - size - offset);
    _size -= size;
    if (_offset >= (offset + size))
        _offset -= size;
    else if (_offset >= offset)
    {
        _offset -= _offset - offset;
        if (_offset > _size)
            _offset = _size;
    }
}

void FBEBuffer::reserve(size_t capacity)
{
    if (capacity > _capacity)
    {
        _capacity = std::max(capacity, 2 * _capacity);
        uint8_t* data = (uint8_t*)std::malloc(_capacity);
        std::memcpy(data, _data, _size);
        std::free(_data);
        _data = data;
    }
}

void FBEBuffer::resize(size_t size)
{
    reserve(size);
    _size = size;
    if (_offset > _size)
        _offset = _size;
}

void FBEBuffer::reset()
{
    _size = 0;
    _offset = 0;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEBaseModel_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding base model
class Model
{
public:
    Model() : Model(nullptr) {}
    Model(const std::shared_ptr<FBEBuffer>& buffer) { _buffer = buffer ? buffer : std::make_shared<FBEBuffer>(); }
    Model(const Model&) = default;
    Model(Model&&) noexcept = default;
    ~Model() = default;

    Model& operator=(const Model&) = default;
    Model& operator=(Model&&) noexcept = default;

    // Get the model buffer
    FBEBuffer& buffer() noexcept { return *_buffer; }
    const FBEBuffer& buffer() const noexcept { return *_buffer; }

    // Attach the model buffer
    void attach(const void* data, size_t size, size_t offset = 0) { _buffer->attach(data, size, offset); }
    void attach(const std::vector<uint8_t>& buffer, size_t offset = 0) { _buffer->attach(buffer, offset); }
    void attach(const FBEBuffer& buffer, size_t offset = 0) { _buffer->attach(buffer.data(), buffer.size(), offset); }

    // Model buffer operations
    size_t allocate(size_t size) { return _buffer->allocate(size); }
    void remove(size_t offset, size_t size) { _buffer->remove(offset, size); }
    void reserve(size_t capacity) { _buffer->reserve(capacity); }
    void resize(size_t size) { _buffer->resize(size); }
    void reset() { _buffer->reset(); }
    void shift(size_t offset) { _buffer->shift(offset); }
    void unshift(size_t offset) { _buffer->unshift(offset); }

private:
    std::shared_ptr<FBEBuffer> _buffer;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModel_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding base field model
template <typename T, typename TBase = T>
class FieldModelBase
{
public:
    FieldModelBase(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return sizeof(TBase); }
    // Get the field extra size
    size_t fbe_extra() const noexcept { return 0; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the value is valid
    bool verify() const noexcept { return true; }

    // Get the field value
    void get(T& value, T defaults = (T)0) const noexcept;
    // Set the field value
    void set(T value) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;
};

// Fast Binary Encoding field model
template <typename T>
class FieldModel : public FieldModelBase<T>
{
public:
    using FieldModelBase<T>::FieldModelBase;
};

// Fast Binary Encoding field model bool specialization
template <>
class FieldModel<bool> : public FieldModelBase<bool, uint8_t>
{
public:
    using FieldModelBase<bool, uint8_t>::FieldModelBase;
};

// Fast Binary Encoding field model char specialization
template <>
class FieldModel<char> : public FieldModelBase<char, uint8_t>
{
public:
    using FieldModelBase<char, uint8_t>::FieldModelBase;
};

// Fast Binary Encoding field model wchar specialization
template <>
class FieldModel<wchar_t> : public FieldModelBase<wchar_t, uint32_t>
{
public:
    using FieldModelBase<wchar_t, uint32_t>::FieldModelBase;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModel_Inline()
{
    std::string code = R"CODE(
template <typename T, typename TBase>
inline void FieldModelBase<T, TBase>::get(T& value, T defaults) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
    {
        value = defaults;
        return;
    }

    value = (T)(*((const TBase*)(_buffer.data() + _buffer.offset() + fbe_offset())));
}

template <typename T, typename TBase>
inline void FieldModelBase<T, TBase>::set(T value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    *((TBase*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (TBase)value;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelDecimal_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model decimal specialization
template <>
class FieldModel<decimal_t>
{
public:
    FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 16; }
    // Get the field extra size
    size_t fbe_extra() const noexcept { return 0; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the decimal value is valid
    bool verify() const noexcept { return true; }

    // Get the decimal value
    void get(decimal_t& value, decimal_t defaults = decimal_t()) const noexcept;
    // Set the decimal value
    void set(decimal_t value) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;

    static uint64_t extract(double a) noexcept;
    static uint64_t uint32x32(uint32_t a, uint32_t b) noexcept;
    static void uint64x64(uint64_t a, uint64_t b, uint64_t& low64, uint32_t& high32) noexcept;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelDecimal_Source()
{
    std::string code = R"CODE(
uint64_t FieldModel<decimal_t>::extract(double a) noexcept
{
    uint64_t result;
    std::memcpy(&result, &a, sizeof(double));
    return result;
}

uint64_t FieldModel<decimal_t>::uint32x32(uint32_t a, uint32_t b) noexcept
{
    return (uint64_t)a * (uint64_t)b;
}

void FieldModel<decimal_t>::uint64x64(uint64_t a, uint64_t b, uint64_t& low64, uint32_t& high32) noexcept
{
    uint64_t low = uint32x32((uint32_t)a, (uint32_t)b);
    uint64_t mid = uint32x32((uint32_t)a, (uint32_t)(b >> 32));
    uint64_t high = uint32x32((uint32_t)(a >> 32), (uint32_t)(b >> 32));
    high += (mid >> 32);
    low += (mid <<= 32);
    // Test for carry
    if (low < mid)
        high++;

    mid = uint32x32((uint32_t)(a >> 32), (uint32_t)b);
    high += (mid >> 32);
    low += (mid <<= 32);
    // Test for carry
    if (low < mid)
        high++;

    if (high > 0xFFFFFFFFu)
    {
        low64 = 0;
        high32 = 0;
    }
    low64 = low;
    high32 = (uint32_t)high;
}

void FieldModel<decimal_t>::get(decimal_t& value, decimal_t defaults) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
    {
        value = defaults;
        return;
    }

    // Value taken via reverse engineering the double that corresponds to 2^64
    const double ds2to64 = 1.8446744073709552e+019;

    // Read decimal parts
    uint64_t low = *((const uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    uint32_t high = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8));
    uint32_t flags = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 12));

    // Calculate decimal value
    double dValue = ((double)low + (double)high * ds2to64) / pow(10.0, (uint8_t)(flags >> 16));
    if (flags & 0x80000000)
        dValue = -dValue;

    value = dValue;
}

void FieldModel<decimal_t>::set(decimal_t value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    // The most we can scale by is 10^28, which is just slightly more
    // than 2^93.  So a float with an exponent of -94 could just
    // barely reach 0.5, but smaller exponents will always round to zero.
    const uint32_t DBLBIAS = 1022;

    // Get exponent value
    double dValue = (double)value;
    int32_t iExp = (int32_t)(((uint32_t)(extract(dValue) >> 52) & 0x7FFu) - DBLBIAS);
    if ((iExp < -94) || (iExp > 96))
    {
        // Value too big for .NET Decimal (exponent is limited to [-94, 96])
        memset((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), 0, 16);
        return;
    }

    uint32_t flags = 0;
    if (dValue < 0)
    {
        dValue = -dValue;
        flags = 0x80000000;
    }

    // Round the input to a 15-digit integer.  The R8 format has
    // only 15 digits of precision, and we want to keep garbage digits
    // out of the Decimal were making.

    // Calculate max power of 10 input value could have by multiplying
    // the exponent by log10(2).  Using scaled integer multiplcation,
    // log10(2) * 2 ^ 16 = .30103 * 65536 = 19728.3.
    int32_t iPower = 14 - ((iExp * 19728) >> 16);

    // iPower is between -14 and 43
    if (iPower >= 0)
    {
        // We have less than 15 digits, scale input up.
        if (iPower > 28)
            iPower = 28;

        dValue *= pow(10.0, iPower);
    }
    else
    {
        if ((iPower != -1) || (dValue >= 1E15))
            dValue /= pow(10.0, -iPower);
        else
            iPower = 0; // didn't scale it
    }

    assert(dValue < 1E15);
    if ((dValue < 1E14) && (iPower < 28))
    {
        dValue *= 10;
        iPower++;
        assert(dValue >= 1E14);
    }

    // Round to int64
    uint64_t ulMant;
    ulMant = (uint64_t)(int64_t)dValue;
    dValue -= (int64_t)ulMant; // difference between input & integer
    if ((dValue > 0.5) || ((dValue == 0.5) && ((ulMant & 1) != 0)))
        ulMant++;

    if (ulMant == 0)
    {
        // Mantissa is 0
        memset((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), 0, 16);
        return;
    }

    if (iPower < 0)
    {
        // Add -iPower factors of 10, -iPower <= (29 - 15) = 14
        iPower = -iPower;
        if (iPower < 10)
        {
            double pow10 = (double)powl(10.0, iPower);
            uint64_t low64 = uint32x32((uint32_t)ulMant, (uint32_t)pow10);
            uint64_t high64 = uint32x32((uint32_t)(ulMant >> 32), (uint32_t)pow10);
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)low64;
            high64 += low64 >> 32;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4)) = (uint32_t)high64;
            high64 >>= 32;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = (uint32_t)high64;
        }
        else
        {
            // Have a big power of 10.
            assert(iPower <= 14);
            uint64_t low64;
            uint32_t high32;
            uint64x64(ulMant, (uint64_t)pow(10.0, iPower), low64, high32);
            *((uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = low64;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = high32;
        }
    }
    else
    {
        // Factor out powers of 10 to reduce the scale, if possible.
        // The maximum number we could factor out would be 14.  This
        // comes from the fact we have a 15-digit number, and the
        // MSD must be non-zero -- but the lower 14 digits could be
        // zero.  Note also the scale factor is never negative, so
        // we can't scale by any more than the power we used to
        // get the integer.
        int lmax = iPower;
        if (lmax > 14)
            lmax = 14;

        if ((((uint8_t)ulMant) == 0) && (lmax >= 8))
        {
            const uint32_t den = 100000000;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 8;
                lmax -= 8;
            }
        }

        if ((((uint32_t)ulMant & 0xF) == 0) && (lmax >= 4))
        {
            const uint32_t den = 10000;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 4;
                lmax -= 4;
            }
        }

        if ((((uint32_t)ulMant & 3) == 0) && (lmax >= 2))
        {
            const uint32_t den = 100;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 2;
                lmax -= 2;
            }
        }

        if ((((uint32_t)ulMant & 1) == 0) && (lmax >= 1))
        {
            const uint32_t den = 10;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower--;
            }
        }

        flags |= (uint32_t)iPower << 16;

        *((uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = ulMant;
        *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = 0;
    }

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 12)) = flags;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelUUID_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model UUID specialization
template <>
class FieldModel<uuid_t>
{
public:
    FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 16; }
    // Get the field extra size
    size_t fbe_extra() const noexcept { return 0; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the UUID value is valid
    bool verify() const noexcept { return true; }

    // Get the UUID value
    void get(uuid_t& value, uuid_t defaults = uuid_t::nil()) const noexcept;
    // Set the UUID value
    void set(uuid_t value) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelUUID_Source()
{
    std::string code = R"CODE(
void FieldModel<uuid_t>::get(uuid_t& value, uuid_t defaults) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
    {
        value = defaults;
        return;
    }

    std::memcpy(value.data().data(), (const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), fbe_size());
}

void FieldModel<uuid_t>::set(uuid_t value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    std::memcpy((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), value.data().data(), fbe_size());
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelBytes_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model bytes specialization
template <>
class FieldModel<buffer_t>
{
public:
    FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 4; }
    // Get the field extra size
    size_t fbe_extra() const noexcept;

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the bytes value is valid
    bool verify() const noexcept;

    // Get the bytes value
    size_t get(void* data, size_t size) const noexcept;
    // Get the bytes value
    template <size_t N>
    size_t get(uint8_t (&data)[N]) const noexcept { return get(data, N); }
    // Get the bytes value
    template <size_t N>
    size_t get(std::array<uint8_t, N>& data) const noexcept { return get(data.data(), data.size()); }
    // Get the bytes value
    void get(std::vector<uint8_t>& value) const noexcept;
    // Get the bytes value
    void get(buffer_t& value) const noexcept { get(value.buffer()); }

    // Set the bytes value
    void set(const void* data, size_t size);
    // Set the bytes value
    template <size_t N>
    void set(const uint8_t (&data)[N]) { set(data, N); }
    // Set the bytes value
    template <size_t N>
    void set(const std::array<uint8_t, N>& data) { set(data.data(), data.size()); }
    // Set the bytes value
    void set(const std::vector<uint8_t>& value) { set(value.data(), value.size()); }
    // Set the bytes value
    void set(const buffer_t& value) { set(value.buffer()); }

private:
    FBEBuffer& _buffer;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelBytes_Source()
{
    std::string code = R"CODE(
size_t FieldModel<buffer_t>::fbe_extra() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_bytes_offset == 0) || ((_buffer.offset() + fbe_bytes_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset));
    return (size_t)(4 + fbe_bytes_size);
}

bool FieldModel<buffer_t>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return true;

    uint32_t fbe_bytes_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_bytes_offset == 0)
        return true;

    if ((_buffer.offset() + fbe_bytes_offset + 4) > _buffer.size())
        return false;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset));
    if ((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) > _buffer.size())
        return false;

    return true;
}

size_t FieldModel<buffer_t>::get(void* data, size_t size) const noexcept
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_bytes_offset == 0)
        return 0;

    assert(((_buffer.offset() + fbe_bytes_offset + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_bytes_offset + 4) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset));
    assert(((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) > _buffer.size())
        return 0;

    size_t result = std::min(size, (size_t)fbe_bytes_size);
    memcpy(data, (const char*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset + 4), result);
    return result;
}

void FieldModel<buffer_t>::get(std::vector<uint8_t>& value) const noexcept
{
    value.clear();

    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_bytes_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_bytes_offset == 0)
        return;

    assert(((_buffer.offset() + fbe_bytes_offset + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_bytes_offset + 4) > _buffer.size())
        return;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset));
    assert(((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) > _buffer.size())
        return;

    const char* fbe_bytes = (const char*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset + 4);
    value.assign(fbe_bytes, fbe_bytes + fbe_bytes_size);
}

void FieldModel<buffer_t>::set(const void* data, size_t size)
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return;

    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_bytes_size = (uint32_t)size;
    uint32_t fbe_bytes_offset = (uint32_t)(_buffer.allocate(4 + fbe_bytes_size) - _buffer.offset());
    assert(((fbe_bytes_offset > 0) && ((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) <= _buffer.size())) && "Model is broken!");
    if ((fbe_bytes_offset == 0) || ((_buffer.offset() + fbe_bytes_offset + 4 + fbe_bytes_size) > _buffer.size()))
        return;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_bytes_offset;
    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset)) = fbe_bytes_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_bytes_offset + 4), data, fbe_bytes_size);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelString_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model string specialization
template <>
class FieldModel<std::string>
{
public:
    FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 4; }
    // Get the field extra size
    size_t fbe_extra() const noexcept;

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the string value is valid
    bool verify() const noexcept;

    // Get the string value
    size_t get(char* data, size_t size) const noexcept;
    // Get the string value
    template <size_t N>
    size_t get(char (&data)[N]) const noexcept { return get(data, N); }
    // Get the string value
    template <size_t N>
    size_t get(std::array<char, N>& data) const noexcept { return get(data.data(), data.size()); }
    // Get the string value
    void get(std::string& value) const noexcept;
    // Get the string value
    void get(std::string& value, const std::string& defaults) const noexcept;

    // Set the string value
    void set(const char* data, size_t size);
    // Set the string value
    template <size_t N>
    void set(const char (&data)[N]) { set(data, N); }
    // Set the string value
    template <size_t N>
    void set(const std::array<char, N>& data) { set(data.data(), data.size()); }
    // Set the string value
    void set(const std::string& value);

private:
    FBEBuffer& _buffer;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelString_Source()
{
    std::string code = R"CODE(
size_t FieldModel<std::string>::fbe_extra() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_string_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_string_offset == 0) || ((_buffer.offset() + fbe_string_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset));
    return (size_t)(4 + fbe_string_size);
}

bool FieldModel<std::string>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return true;

    uint32_t fbe_string_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_string_offset == 0)
        return true;

    if ((_buffer.offset() + fbe_string_offset + 4) > _buffer.size())
        return false;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset));
    if ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size())
        return false;

    return true;
}

size_t FieldModel<std::string>::get(char* data, size_t size) const noexcept
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_string_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_string_offset == 0)
        return 0;

    assert(((_buffer.offset() + fbe_string_offset + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4) > _buffer.size())
        return 0;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset));
    assert(((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size())
        return 0;

    size_t result = std::min(size, (size_t)fbe_string_size);
    memcpy(data, (const char*)(_buffer.data() + _buffer.offset() + fbe_string_offset + 4), result);
    return result;
}

void FieldModel<std::string>::get(std::string& value) const noexcept
{
    value.clear();

    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_string_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + _offset));
    if (fbe_string_offset == 0)
        return;

    assert(((_buffer.offset() + fbe_string_offset + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4) > _buffer.size())
        return;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset));
    assert(((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size())
        return;

    value.assign((const char*)(_buffer.data() + _buffer.offset() + fbe_string_offset + 4), fbe_string_size);
}

void FieldModel<std::string>::get(std::string& value, const std::string& defaults) const noexcept
{
    value = defaults;

    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_string_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + _offset));
    if (fbe_string_offset == 0)
        return;

    assert(((_buffer.offset() + fbe_string_offset + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4) > _buffer.size())
        return;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset));
    assert(((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size())
        return;

    value.assign((const char*)(_buffer.data() + _buffer.offset() + fbe_string_offset + 4), fbe_string_size);
}

void FieldModel<std::string>::set(const char* data, size_t size)
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return;

    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_string_size = (uint32_t)size;
    uint32_t fbe_string_offset = (uint32_t)(_buffer.allocate(4 + fbe_string_size) - _buffer.offset());
    assert(((fbe_string_offset > 0) && ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) <= _buffer.size())) && "Model is broken!");
    if ((fbe_string_offset == 0) || ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size()))
        return;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_string_offset;
    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset)) = fbe_string_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_string_offset + 4), data, fbe_string_size);
}

void FieldModel<std::string>::set(const std::string& value)
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    uint32_t fbe_string_size = (uint32_t)value.size();
    uint32_t fbe_string_offset = (uint32_t)(_buffer.allocate(4 + fbe_string_size) - _buffer.offset());
    assert(((fbe_string_offset > 0) && ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) <= _buffer.size())) && "Model is broken!");
    if ((fbe_string_offset == 0) || ((_buffer.offset() + fbe_string_offset + 4 + fbe_string_size) > _buffer.size()))
        return;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_string_offset;
    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_string_offset)) = fbe_string_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_string_offset + 4), value.data(), fbe_string_size);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelOptional_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model optional specialization
template <typename T>
class FieldModel<std::optional<T>>
{
public:
    FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset), value(buffer, 0) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 1 + 4; }
    // Get the field extra size
    size_t fbe_extra() const noexcept;

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    //! Is the value present?
    explicit operator bool() const noexcept { return has_value(); }

    // Checks if the object contains a value
    bool has_value() const noexcept;

    // Check if the optional value is valid
    bool verify() const noexcept;

    // Get the optional value (being phase)
    size_t get_begin() const noexcept;
    // Get the optional value (end phase)
    void get_end(size_t fbe_begin) const noexcept;

    // Get the optional value
    void get(std::optional<T>& opt, const std::optional<T>& defaults = std::nullopt) const noexcept;

    // Set the optional value (begin phase)
    size_t set_begin(bool has_value);
    // Set the optional value (end phase)
    void set_end(size_t fbe_begin);

    // Set the optional value
    void set(const std::optional<T>& opt);

private:
    FBEBuffer& _buffer;
    size_t _offset;

public:
    // Base field model value
    FieldModel<T> value;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelOptional_Inline()
{
    std::string code = R"CODE(
template <typename T>
inline size_t FieldModel<std::optional<T>>::fbe_extra() const noexcept
{
    if (!has_value())
        return 0;

    uint32_t fbe_optional_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 1));
    if ((fbe_optional_offset == 0) || ((_buffer.offset() + fbe_optional_offset + 4) > _buffer.size()))
        return 0;

    _buffer.shift(fbe_optional_offset);
    size_t fbe_result = value.fbe_size() + value.fbe_extra();
    _buffer.unshift(fbe_optional_offset);
    return fbe_result;
}

template <typename T>
inline bool FieldModel<std::optional<T>>::has_value() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return false;

    uint8_t fbe_has_value = *((const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    return (fbe_has_value != 0);
}

template <typename T>
inline bool FieldModel<std::optional<T>>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return true;

    uint8_t fbe_has_value = *((const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_has_value == 0)
        return true;

    uint32_t fbe_optional_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 1));
    if (fbe_optional_offset == 0)
        return false;

    _buffer.shift(fbe_optional_offset);
    bool fbe_result = value.verify();
    _buffer.unshift(fbe_optional_offset);
    return fbe_result;
}

template <typename T>
inline size_t FieldModel<std::optional<T>>::get_begin() const noexcept
{
    if (!has_value())
        return 0;

    uint32_t fbe_optional_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 1));
    assert((fbe_optional_offset > 0) && "Model is broken!");
    if (fbe_optional_offset == 0)
        return 0;

    _buffer.shift(fbe_optional_offset);
    return fbe_optional_offset;
}

template <typename T>
inline void FieldModel<std::optional<T>>::get_end(size_t fbe_begin) const noexcept
{
    _buffer.unshift(fbe_begin);
}

template <typename T>
inline void FieldModel<std::optional<T>>::get(std::optional<T>& opt, const std::optional<T>& defaults) const noexcept
{
    opt = defaults;

    size_t fbe_begin = get_begin();
    if (fbe_begin == 0)
        return;

    T temp = T();
    value.get(temp);
    opt.emplace(temp);

    get_end(fbe_begin);
}

template <typename T>
inline size_t FieldModel<std::optional<T>>::set_begin(bool has_value)
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint8_t fbe_has_value = has_value ? 1 : 0;
    *((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_has_value;
    if (fbe_has_value == 0)
        return 0;

    uint32_t fbe_optional_size = (uint32_t)value.fbe_size();
    uint32_t fbe_optional_offset = (uint32_t)(_buffer.allocate(fbe_optional_size) - _buffer.offset());
    assert(((fbe_optional_offset > 0) && ((_buffer.offset() + fbe_optional_offset + fbe_optional_size) <= _buffer.size())) && "Model is broken!");
    if ((fbe_optional_offset == 0) || ((_buffer.offset() + fbe_optional_offset + fbe_optional_size) > _buffer.size()))
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 1)) = fbe_optional_offset;

    _buffer.shift(fbe_optional_offset);
    return fbe_optional_offset;
}

template <typename T>
inline void FieldModel<std::optional<T>>::set_end(size_t fbe_begin)
{
    _buffer.unshift(fbe_begin);
}

template <typename T>
inline void FieldModel<std::optional<T>>::set(const std::optional<T>& opt)
{
    size_t fbe_begin = set_begin(opt.has_value());
    if (fbe_begin == 0)
        return;

    if (opt)
        value.set(opt.value());

    set_end(fbe_begin);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelArray_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model array
template <typename T, size_t N>
class FieldModelArray
{
public:
    FieldModelArray(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset), _model(buffer, offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return N * _model.fbe_size(); }
    // Get the field extra size
    size_t fbe_extra() const noexcept { return 0; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Get the array
    const uint8_t* data() const noexcept;
    // Get the array
    uint8_t* data() noexcept;
    // Get the array offset
    size_t offset() const noexcept { return 0; }
    // Get the array size
    size_t size() const noexcept { return N; }

    // Array index operator
    FieldModel<T> operator[](size_t index) const noexcept;

    // Check if the array is valid
    bool verify() const noexcept;

    // Get the array as C-array
    template <size_t S>
    void get(T (&values)[S]) const noexcept;
    // Get the array as std::array
    template <size_t S>
    void get(std::array<T, S>& values) const noexcept;
    // Get the array as std::vector
    void get(std::vector<T>& values) const noexcept;

    // Set the array as C-array
    template <size_t S>
    void set(const T (&values)[S]) noexcept;
    // Set the array as std::array
    template <size_t S>
    void set(const std::array<T, S>& values) noexcept;
    // Set the array as std::vector
    void set(const std::vector<T>& values) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;
    FieldModel<T> _model;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelArray_Inline()
{
    std::string code = R"CODE(
template <typename T, size_t N>
inline const uint8_t* FieldModelArray<T, N>::data() const noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    return _buffer.data() + _buffer.offset() + fbe_offset();
}

template <typename T, size_t N>
inline uint8_t* FieldModelArray<T, N>::data() noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    return _buffer.data() + _buffer.offset() + fbe_offset();
}

template <typename T, size_t N>
inline FieldModel<T> FieldModelArray<T, N>::operator[](size_t index) const noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    assert((index < N) && "Index is out of bounds!");

    FieldModel<T> fbe_model(_buffer, fbe_offset());
    fbe_model.fbe_shift(index * fbe_model.fbe_size());
    return fbe_model;
}

template <typename T, size_t N>
inline bool FieldModelArray<T, N>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return false;

    FieldModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = N; i-- > 0;)
    {
        if (!fbe_model.verify())
            return false;
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }

    return true;
}

template <typename T, size_t N>
template <size_t S>
inline void FieldModelArray<T, N>::get(T (&values)[S]) const noexcept
{
    auto fbe_model = (*this)[0];
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        fbe_model.get(values[i]);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T, size_t N>
template <size_t S>
inline void FieldModelArray<T, N>::get(std::array<T, S>& values) const noexcept
{
    auto fbe_model = (*this)[0];
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        fbe_model.get(values[i]);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T, size_t N>
inline void FieldModelArray<T, N>::get(std::vector<T>& values) const noexcept
{
    values.clear();
    values.reserve(N);

    auto fbe_model = (*this)[0];
    for (size_t i = N; i-- > 0;)
    {
        T value = T();
        fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T, size_t N>
template <size_t S>
inline void FieldModelArray<T, N>::set(const T (&values)[S]) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        fbe_model.set(values[i]);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T, size_t N>
template <size_t S>
inline void FieldModelArray<T, N>::set(const std::array<T, S>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        fbe_model.set(values[i]);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T, size_t N>
inline void FieldModelArray<T, N>::set(const std::vector<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = 0; (i < values.size()) && (i < N); ++i)
    {
        fbe_model.set(values[i]);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelVector_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model vector
template <typename T>
class FieldModelVector
{
public:
    FieldModelVector(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 4; }
    // Get the field extra size
    size_t fbe_extra() const noexcept;

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Get the vector offset
    size_t offset() const noexcept;
    // Get the vector size
    size_t size() const noexcept;

    // Vector index operator
    FieldModel<T> operator[](size_t index) const noexcept;

    // Resize the vector and get its first model
    FieldModel<T> resize(size_t size);

    // Check if the vector is valid
    bool verify() const noexcept;

    // Get the vector as std::vector
    void get(std::vector<T>& values) const noexcept;
    // Get the vector as std::list
    void get(std::list<T>& values) const noexcept;
    // Get the vector as std::set
    void get(std::set<T>& values) const noexcept;

    // Set the vector as std::vector
    void set(const std::vector<T>& values) noexcept;
    // Set the vector as std::list
    void set(const std::list<T>& values) noexcept;
    // Set the vector as std::set
    void set(const std::set<T>& values) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelVector_Inline()
{
    std::string code = R"CODE(
template <typename T>
inline size_t FieldModelVector<T>::fbe_extra() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_vector_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_vector_offset == 0) || ((_buffer.offset() + fbe_vector_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_vector_offset));

    size_t fbe_result = 4;
    FieldModel<T> fbe_model(_buffer, fbe_vector_offset + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        fbe_result += fbe_model.fbe_size() + fbe_model.fbe_extra();
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
    return fbe_result;
}

template <typename T>
inline size_t FieldModelVector<T>::offset() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_vector_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    return fbe_vector_offset;
}

template <typename T>
inline size_t FieldModelVector<T>::size() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_vector_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_vector_offset == 0) || ((_buffer.offset() + fbe_vector_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_vector_offset));
    return fbe_vector_size;
}

template <typename T>
inline FieldModel<T> FieldModelVector<T>::operator[](size_t index) const noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");

    uint32_t fbe_vector_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((fbe_vector_offset > 0) && ((_buffer.offset() + fbe_vector_offset + 4) <= _buffer.size())) && "Model is broken!");

    [[maybe_unused]] uint32_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_vector_offset));
    assert((index < fbe_vector_size) && "Index is out of bounds!");

    FieldModel<T> fbe_model(_buffer, fbe_vector_offset + 4);
    fbe_model.fbe_shift(index * fbe_model.fbe_size());
    return fbe_model;
}

template <typename T>
inline FieldModel<T> FieldModelVector<T>::resize(size_t size)
{
    FieldModel<T> fbe_model(_buffer, fbe_offset());

    uint32_t fbe_vector_size = (uint32_t)(size * fbe_model.fbe_size());
    uint32_t fbe_vector_offset = (uint32_t)(_buffer.allocate(4 + fbe_vector_size) - _buffer.offset());
    assert(((fbe_vector_offset > 0) && ((_buffer.offset() + fbe_vector_offset + 4) <= _buffer.size())) && "Model is broken!");

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_vector_offset;
    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_vector_offset)) = (uint32_t)size;

    memset((char*)(_buffer.data() + _buffer.offset() + fbe_vector_offset + 4), 0, fbe_vector_size);

    return FieldModel<T>(_buffer, fbe_vector_offset + 4);
}

template <typename T>
inline bool FieldModelVector<T>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return true;

    uint32_t fbe_vector_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_vector_offset == 0)
        return true;

    if ((_buffer.offset() + fbe_vector_offset + 4) > _buffer.size())
        return false;

    uint32_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_vector_offset));

    FieldModel<T> fbe_model(_buffer, fbe_vector_offset + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        if (!fbe_model.verify())
            return false;
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }

    return true;
}

template <typename T>
inline void FieldModelVector<T>::get(std::vector<T>& values) const noexcept
{
    values.clear();

    size_t fbe_vector_size = size();
    if (fbe_vector_size == 0)
        return;

    values.reserve(fbe_vector_size);

    auto fbe_model = (*this)[0];
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T>
inline void FieldModelVector<T>::get(std::list<T>& values) const noexcept
{
    values.clear();

    size_t fbe_vector_size = size();
    if (fbe_vector_size == 0)
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T>
inline void FieldModelVector<T>::get(std::set<T>& values) const noexcept
{
    values.clear();

    size_t fbe_vector_size = size();
    if (fbe_vector_size == 0)
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        fbe_model.get(value);
        values.emplace(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T>
inline void FieldModelVector<T>::set(const std::vector<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = resize(values.size());
    for (const auto& value : values)
    {
        fbe_model.set(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T>
inline void FieldModelVector<T>::set(const std::list<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = resize(values.size());
    for (const auto& value : values)
    {
        fbe_model.set(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}

template <typename T>
inline void FieldModelVector<T>::set(const std::set<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = resize(values.size());
    for (const auto& value : values)
    {
        fbe_model.set(value);
        fbe_model.fbe_shift(fbe_model.fbe_size());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelMap_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding field model map
template <typename TKey, typename TValue>
class FieldModelMap
{
public:
    FieldModelMap(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Get the field size
    size_t fbe_size() const noexcept { return 4; }
    // Get the field extra size
    size_t fbe_extra() const noexcept;

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Get the map offset
    size_t offset() const noexcept;
    // Get the map size
    size_t size() const noexcept;

    // Map index operator
    std::pair<FieldModel<TKey>, FieldModel<TValue>> operator[](size_t index) const noexcept;

    // Resize the map and get its first model
    std::pair<FieldModel<TKey>, FieldModel<TValue>> resize(size_t size);

    // Check if the map is valid
    bool verify() const noexcept;

    // Get the map as std::map
    void get(std::map<TKey, TValue>& values) const noexcept;
    // Get the map as std::unordered_map
    void get(std::unordered_map<TKey, TValue>& values) const noexcept;

    // Set the map as std::map
    void set(const std::map<TKey, TValue>& values) noexcept;
    // Set the map as std::unordered_map
    void set(const std::unordered_map<TKey, TValue>& values) noexcept;

private:
    FBEBuffer& _buffer;
    size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFieldModelMap_Inline()
{
    std::string code = R"CODE(
template <typename TKey, typename TValue>
inline size_t FieldModelMap<TKey, TValue>::fbe_extra() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_map_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_map_offset == 0) || ((_buffer.offset() + fbe_map_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_map_offset));

    size_t fbe_result = 4;
    FieldModel<TKey> fbe_model_key(_buffer, fbe_map_offset + 4);
    FieldModel<TValue> fbe_model_value(_buffer, fbe_map_offset + 4 + fbe_model_key.fbe_size());
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        fbe_result += fbe_model_key.fbe_size() + fbe_model_key.fbe_extra();
        fbe_model_key.fbe_shift(fbe_model_key.fbe_size() + fbe_model_value.fbe_size());
        fbe_result += fbe_model_value.fbe_size() + fbe_model_value.fbe_extra();
        fbe_model_value.fbe_shift(fbe_model_key.fbe_size() + fbe_model_value.fbe_size());
    }
    return fbe_result;
}

template <typename TKey, typename TValue>
inline size_t FieldModelMap<TKey, TValue>::offset() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_map_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    return fbe_map_offset;
}

template <typename TKey, typename TValue>
inline size_t FieldModelMap<TKey, TValue>::size() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    uint32_t fbe_map_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((fbe_map_offset == 0) || ((_buffer.offset() + fbe_map_offset + 4) > _buffer.size()))
        return 0;

    uint32_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_map_offset));
    return fbe_map_size;
}

template <typename TKey, typename TValue>
inline std::pair<FieldModel<TKey>, FieldModel<TValue>> FieldModelMap<TKey, TValue>::operator[](size_t index) const noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");

    uint32_t fbe_map_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((fbe_map_offset > 0) && ((_buffer.offset() + fbe_map_offset + 4) <= _buffer.size())) && "Model is broken!");

    [[maybe_unused]] uint32_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_map_offset));
    assert((index < fbe_map_size) && "Index is out of bounds!");

    FieldModel<TKey> fbe_model_key(_buffer, fbe_map_offset + 4);
    FieldModel<TValue> fbe_model_value(_buffer, fbe_map_offset + 4 + fbe_model_key.fbe_size());
    fbe_model_key.fbe_shift(index * (fbe_model_key.fbe_size() + fbe_model_value.fbe_size()));
    fbe_model_value.fbe_shift(index * (fbe_model_key.fbe_size() + fbe_model_value.fbe_size()));
    return std::make_pair(fbe_model_key, fbe_model_value);
}

template <typename TKey, typename TValue>
inline std::pair<FieldModel<TKey>, FieldModel<TValue>> FieldModelMap<TKey, TValue>::resize(size_t size)
{
    FieldModel<TKey> fbe_model_key(_buffer, fbe_offset());
    FieldModel<TValue> fbe_model_value(_buffer, fbe_offset() + fbe_model_key.fbe_size());

    uint32_t fbe_map_size = (uint32_t)(size * (fbe_model_key.fbe_size() + fbe_model_value.fbe_size()));
    uint32_t fbe_map_offset = (uint32_t)(_buffer.allocate(4 + fbe_map_size) - _buffer.offset());
    assert(((fbe_map_offset > 0) && ((_buffer.offset() + fbe_map_offset + 4 + fbe_map_size) <= _buffer.size())) && "Model is broken!");

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_map_offset;
    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_map_offset)) = (uint32_t)size;

    memset((char*)(_buffer.data() + _buffer.offset() + fbe_map_offset + 4), 0, fbe_map_size);

    return std::make_pair(FieldModel<TKey>(_buffer, fbe_map_offset + 4), FieldModel<TValue>(_buffer, fbe_map_offset + 4 + fbe_model_key.fbe_size()));
}

template <typename TKey, typename TValue>
inline bool FieldModelMap<TKey, TValue>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return true;

    uint32_t fbe_map_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_map_offset == 0)
        return true;

    if ((_buffer.offset() + fbe_map_offset + 4) > _buffer.size())
        return false;

    uint32_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_map_offset));

    FieldModel<TKey> fbe_model_key(_buffer, fbe_map_offset + 4);
    FieldModel<TValue> fbe_model_value(_buffer, fbe_map_offset + 4 + fbe_model_key.fbe_size());
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        if (!fbe_model_key.verify())
            return false;
        fbe_model_key.fbe_shift(fbe_model_key.fbe_size() + fbe_model_value.fbe_size());
        if (!fbe_model_value.verify())
            return false;
        fbe_model_value.fbe_shift(fbe_model_key.fbe_size() + fbe_model_value.fbe_size());
    }

    return true;
}

template <typename TKey, typename TValue>
inline void FieldModelMap<TKey, TValue>::get(std::map<TKey, TValue>& values) const noexcept
{
    values.clear();

    size_t fbe_map_size = size();
    if (fbe_map_size == 0)
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        TKey key = TKey();
        TValue value = TValue();
        fbe_model.first.get(key);
        fbe_model.second.get(value);
        values.emplace(key, value);
        fbe_model.first.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
        fbe_model.second.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
    }
}

template <typename TKey, typename TValue>
inline void FieldModelMap<TKey, TValue>::get(std::unordered_map<TKey, TValue>& values) const noexcept
{
    values.clear();

    size_t fbe_map_size = size();
    if (fbe_map_size == 0)
        return;

    auto fbe_model = (*this)[0];
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        TKey key = TKey();
        TValue value = TValue();
        fbe_model.first.get(key);
        fbe_model.second.get(value);
        values.emplace(key, value);
        fbe_model.first.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
        fbe_model.second.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
    }
}

template <typename TKey, typename TValue>
inline void FieldModelMap<TKey, TValue>::set(const std::map<TKey, TValue>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = resize(values.size());
    for (const auto& value : values)
    {
        fbe_model.first.set(value.first);
        fbe_model.first.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
        fbe_model.second.set(value.second);
        fbe_model.second.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
    }
}

template <typename TKey, typename TValue>
inline void FieldModelMap<TKey, TValue>::set(const std::unordered_map<TKey, TValue>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return;

    auto fbe_model = resize(values.size());
    for (const auto& value : values)
    {
        fbe_model.first.set(value.first);
        fbe_model.first.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
        fbe_model.second.set(value.second);
        fbe_model.second.fbe_shift(fbe_model.first.fbe_size() + fbe_model.second.fbe_size());
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModel_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding base final model
template <typename T, typename TBase = T>
class FinalModelBase
{
public:
    FinalModelBase(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(T value) const noexcept { return fbe_size(); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Get the final size
    size_t fbe_size() const noexcept { return sizeof(TBase); }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the value is valid
    size_t verify() const noexcept;

    // Get the field value
    size_t get(T& value) const noexcept;
    // Set the field value
    size_t set(T value) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};

// Fast Binary Encoding final model
template <typename T>
class FinalModel : public FinalModelBase<T>
{
public:
    using FinalModelBase<T>::FinalModelBase;
};

// Fast Binary Encoding final model bool specialization
template <>
class FinalModel<bool> : public FinalModelBase<bool, uint8_t>
{
public:
    using FinalModelBase<bool, uint8_t>::FinalModelBase;
};

// Fast Binary Encoding final model char specialization
template <>
class FinalModel<char> : public FinalModelBase<char, uint8_t>
{
public:
    using FinalModelBase<char, uint8_t>::FinalModelBase;
};

// Fast Binary Encoding final model wchar specialization
template <>
class FinalModel<wchar_t> : public FinalModelBase<wchar_t, uint32_t>
{
public:
    using FinalModelBase<wchar_t, uint32_t>::FinalModelBase;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModel_Inline()
{
    std::string code = R"CODE(
template <typename T, typename TBase>
inline size_t FinalModelBase<T, TBase>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    return fbe_size();
}

template <typename T, typename TBase>
inline size_t FinalModelBase<T, TBase>::get(T& value) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    value = (T)(*((const TBase*)(_buffer.data() + _buffer.offset() + fbe_offset())));
    return fbe_size();
}

template <typename T, typename TBase>
inline size_t FinalModelBase<T, TBase>::set(T value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    *((TBase*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (TBase)value;
    return fbe_size();
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelDecimal_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model decimal specialization
template <>
class FinalModel<decimal_t>
{
public:
    FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(decimal_t value) const noexcept { return fbe_size(); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Get the final size
    size_t fbe_size() const noexcept { return 16; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the decimal value is valid
    size_t verify() const noexcept;

    // Get the decimal value
    size_t get(decimal_t& value) const noexcept;
    // Set the decimal value
    size_t set(decimal_t value) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;

    static uint64_t extract(double a) noexcept;
    static uint64_t uint32x32(uint32_t a, uint32_t b) noexcept;
    static void uint64x64(uint64_t a, uint64_t b, uint64_t& low64, uint32_t& high32) noexcept;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelDecimal_Source()
{
    std::string code = R"CODE(
uint64_t FinalModel<decimal_t>::extract(double a) noexcept
{
    uint64_t result;
    std::memcpy(&result, &a, sizeof(double));
    return result;
}

uint64_t FinalModel<decimal_t>::uint32x32(uint32_t a, uint32_t b) noexcept
{
    return (uint64_t)a * (uint64_t)b;
}

void FinalModel<decimal_t>::uint64x64(uint64_t a, uint64_t b, uint64_t& low64, uint32_t& high32) noexcept
{
    uint64_t low = uint32x32((uint32_t)a, (uint32_t)b);
    uint64_t mid = uint32x32((uint32_t)a, (uint32_t)(b >> 32));
    uint64_t high = uint32x32((uint32_t)(a >> 32), (uint32_t)(b >> 32));
    high += (mid >> 32);
    low += (mid <<= 32);
    // Test for carry
    if (low < mid)
        high++;

    mid = uint32x32((uint32_t)(a >> 32), (uint32_t)b);
    high += (mid >> 32);
    low += (mid <<= 32);
    // Test for carry
    if (low < mid)
        high++;

    if (high > 0xFFFFFFFFu)
    {
        low64 = 0;
        high32 = 0;
    }
    low64 = low;
    high32 = (uint32_t)high;
}

size_t FinalModel<decimal_t>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    return fbe_size();
}

size_t FinalModel<decimal_t>::get(decimal_t& value) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    // Value taken via reverse engineering the double that corresponds to 2^64
    const double ds2to64 = 1.8446744073709552e+019;

    // Read decimal parts
    uint64_t low = *((const uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    uint32_t high = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8));
    uint32_t flags = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 12));

    // Calculate decimal value
    double dValue = ((double)low + (double)high * ds2to64) / pow(10.0, (uint8_t)(flags >> 16));
    if (flags & 0x80000000)
        dValue = -dValue;

    value = dValue;
    return fbe_size();
}

size_t FinalModel<decimal_t>::set(decimal_t value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    // The most we can scale by is 10^28, which is just slightly more
    // than 2^93.  So a float with an exponent of -94 could just
    // barely reach 0.5, but smaller exponents will always round to zero.
    const uint32_t DBLBIAS = 1022;

    // Get exponent value
    double dValue = (double)value;
    int32_t iExp = (int32_t)(((uint32_t)(extract(dValue) >> 52) & 0x7FFu) - DBLBIAS);
    if ((iExp < -94) || (iExp > 96))
    {
        // Value too big for .NET Decimal (exponent is limited to [-94, 96])
        memset((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), 0, 16);
        return fbe_size();
    }

    uint32_t flags = 0;
    if (dValue < 0)
    {
        dValue = -dValue;
        flags = 0x80000000;
    }

    // Round the input to a 15-digit integer.  The R8 format has
    // only 15 digits of precision, and we want to keep garbage digits
    // out of the Decimal were making.

    // Calculate max power of 10 input value could have by multiplying
    // the exponent by log10(2).  Using scaled integer multiplcation,
    // log10(2) * 2 ^ 16 = .30103 * 65536 = 19728.3.
    int32_t iPower = 14 - ((iExp * 19728) >> 16);

    // iPower is between -14 and 43
    if (iPower >= 0)
    {
        // We have less than 15 digits, scale input up.
        if (iPower > 28)
            iPower = 28;

        dValue *= pow(10.0, iPower);
    }
    else
    {
        if ((iPower != -1) || (dValue >= 1E15))
            dValue /= pow(10.0, -iPower);
        else
            iPower = 0; // didn't scale it
    }

    assert(dValue < 1E15);
    if ((dValue < 1E14) && (iPower < 28))
    {
        dValue *= 10;
        iPower++;
        assert(dValue >= 1E14);
    }

    // Round to int64
    uint64_t ulMant;
    ulMant = (uint64_t)(int64_t)dValue;
    dValue -= (int64_t)ulMant; // difference between input & integer
    if ((dValue > 0.5) || ((dValue == 0.5) && ((ulMant & 1) != 0)))
        ulMant++;

    if (ulMant == 0)
    {
        // Mantissa is 0
        memset((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), 0, 16);
        return fbe_size();
    }

    if (iPower < 0)
    {
        // Add -iPower factors of 10, -iPower <= (29 - 15) = 14
        iPower = -iPower;
        if (iPower < 10)
        {
            double pow10 = (double)powl(10.0, iPower);
            uint64_t low64 = uint32x32((uint32_t)ulMant, (uint32_t)pow10);
            uint64_t high64 = uint32x32((uint32_t)(ulMant >> 32), (uint32_t)pow10);
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)low64;
            high64 += low64 >> 32;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4)) = (uint32_t)high64;
            high64 >>= 32;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = (uint32_t)high64;
        }
        else
        {
            // Have a big power of 10.
            assert(iPower <= 14);
            uint64_t low64;
            uint32_t high32;
            uint64x64(ulMant, (uint64_t)pow(10.0, iPower), low64, high32);
            *((uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = low64;
            *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = high32;
        }
    }
    else
    {
        // Factor out powers of 10 to reduce the scale, if possible.
        // The maximum number we could factor out would be 14.  This
        // comes from the fact we have a 15-digit number, and the
        // MSD must be non-zero -- but the lower 14 digits could be
        // zero.  Note also the scale factor is never negative, so
        // we can't scale by any more than the power we used to
        // get the integer.
        int lmax = iPower;
        if (lmax > 14)
            lmax = 14;

        if ((((uint8_t)ulMant) == 0) && (lmax >= 8))
        {
            const uint32_t den = 100000000;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 8;
                lmax -= 8;
            }
        }

        if ((((uint32_t)ulMant & 0xF) == 0) && (lmax >= 4))
        {
            const uint32_t den = 10000;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 4;
                lmax -= 4;
            }
        }

        if ((((uint32_t)ulMant & 3) == 0) && (lmax >= 2))
        {
            const uint32_t den = 100;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower -= 2;
                lmax -= 2;
            }
        }

        if ((((uint32_t)ulMant & 1) == 0) && (lmax >= 1))
        {
            const uint32_t den = 10;
            uint64_t div = ulMant / den;
            if ((uint32_t)ulMant == (uint32_t)(div * den))
            {
                ulMant = div;
                iPower--;
            }
        }

        flags |= (uint32_t)iPower << 16;

        *((uint64_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = ulMant;
        *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 8)) = 0;
    }

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset() + 12)) = flags;
    return fbe_size();
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelUUID_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model UUID specialization
template <>
class FinalModel<uuid_t>
{
public:
    FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(uuid_t value) const noexcept { return fbe_size(); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Get the final size
    size_t fbe_size() const noexcept { return 16; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the UUID value is valid
    size_t verify() const noexcept;

    // Get the UUID value
    size_t get(uuid_t& value) const noexcept;
    // Set the UUID value
    size_t set(uuid_t value) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelUUID_Source()
{
    std::string code = R"CODE(
size_t FinalModel<uuid_t>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    return fbe_size();
}

size_t FinalModel<uuid_t>::get(uuid_t& value) const noexcept
{
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    std::memcpy(value.data().data(), (const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), fbe_size());
    return fbe_size();
}

size_t FinalModel<uuid_t>::set(uuid_t value) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())
        return 0;

    std::memcpy((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()), value.data().data(), fbe_size());
    return fbe_size();
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelBytes_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model bytes specialization
template <>
class FinalModel<buffer_t>
{
public:
    FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(const void* data, size_t size) const noexcept { return 4 + size; }
    template <size_t N>
    size_t fbe_allocation_size(const uint8_t (&data)[N]) const noexcept { return 4 + N; }
    template <size_t N>
    size_t fbe_allocation_size(const std::array<uint8_t, N>& data) const noexcept { return 4 + N; }
    size_t fbe_allocation_size(const std::vector<uint8_t>& value) const noexcept { return 4 + value.size(); }
    size_t fbe_allocation_size(const buffer_t& value) const noexcept { return 4 + value.size(); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the bytes value is valid
    size_t verify() const noexcept;

    // Get the bytes value
    size_t get(void* data, size_t size) const noexcept;
    // Get the bytes value
    template <size_t N>
    size_t get(uint8_t (&data)[N]) const noexcept { return get(data, N); }
    // Get the bytes value
    template <size_t N>
    size_t get(std::array<uint8_t, N>& data) const noexcept { return get(data.data(), data.size()); }
    // Get the bytes value
    size_t get(std::vector<uint8_t>& value) const noexcept;
    // Get the bytes value
    size_t get(buffer_t& value) const noexcept { return get(value.buffer()); }

    // Set the bytes value
    size_t set(const void* data, size_t size);
    // Set the bytes value
    template <size_t N>
    size_t set(const uint8_t (&data)[N]) { return set(data, N); }
    // Set the bytes value
    template <size_t N>
    size_t set(const std::array<uint8_t, N>& data) { return set(data.data(), data.size()); }
    // Set the bytes value
    size_t set(const std::vector<uint8_t>& value) { return set(value.data(), value.size()); }
    // Set the bytes value
    size_t set(const buffer_t& value) { return set(value.buffer()); }

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelBytes_Source()
{
    std::string code = R"CODE(
size_t FinalModel<buffer_t>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    return 4 + fbe_bytes_size;
}

size_t FinalModel<buffer_t>::get(void* data, size_t size) const noexcept
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) > _buffer.size())
        return 4;

    size_t result = std::min(size, (size_t)fbe_bytes_size);
    memcpy(data, (const char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), result);
    return 4 + fbe_bytes_size;
}

size_t FinalModel<buffer_t>::get(std::vector<uint8_t>& value) const noexcept
{
    value.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) > _buffer.size())
        return 4;

    const char* fbe_bytes = (const char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4);
    value.assign(fbe_bytes, fbe_bytes + fbe_bytes_size);
    return 4 + fbe_bytes_size;
}

size_t FinalModel<buffer_t>::set(const void* data, size_t size)
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_bytes_size = (uint32_t)size;
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_bytes_size) > _buffer.size())
        return 4;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_bytes_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), data, fbe_bytes_size);
    return 4 + fbe_bytes_size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelString_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model string specialization
template <>
class FinalModel<std::string>
{
public:
    FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(const char* data, size_t size) const noexcept { return 4 + size; }
    template <size_t N>
    size_t fbe_allocation_size(const char (&data)[N]) const noexcept { return 4 + N; }
    template <size_t N>
    size_t fbe_allocation_size(const std::array<char, N>& data) const noexcept { return 4 + N; }
    size_t fbe_allocation_size(const std::string& value) const noexcept { return 4 + value.size(); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the string value is valid
    size_t verify() const noexcept;

    // Get the string value
    size_t get(char* data, size_t size) const noexcept;
    // Get the string value
    template <size_t N>
    size_t get(char (&data)[N]) const noexcept { return get(data, N); }
    // Get the string value
    template <size_t N>
    size_t get(std::array<char, N>& data) const noexcept { return get(data.data(), data.size()); }
    // Get the string value
    size_t get(std::string& value) const noexcept;

    // Set the string value
    size_t set(const char* data, size_t size);
    // Set the string value
    template <size_t N>
    size_t set(const char (&data)[N]) { return set(data, N); }
    // Set the string value
    template <size_t N>
    size_t set(const std::array<char, N>& data) { return set(data.data(), data.size()); }
    // Set the string value
    size_t set(const std::string& value);

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelString_Source()
{
    std::string code = R"CODE(
size_t FinalModel<std::string>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    return 4 + fbe_string_size;
}

size_t FinalModel<std::string>::get(char* data, size_t size) const noexcept
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) > _buffer.size())
        return 4;

    size_t result = std::min(size, (size_t)fbe_string_size);
    memcpy(data, (const char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), result);
    return 4 + fbe_string_size;
}

size_t FinalModel<std::string>::get(std::string& value) const noexcept
{
    value.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_string_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) > _buffer.size())
        return 4;

    value.assign((const char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), fbe_string_size);
    return 4 + fbe_string_size;
}

size_t FinalModel<std::string>::set(const char* data, size_t size)
{
    assert(((size == 0) || (data != nullptr)) && "Invalid buffer!");
    if ((size > 0) && (data == nullptr))
        return 0;

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_string_size = (uint32_t)size;
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) > _buffer.size())
        return 4;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_string_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), data, fbe_string_size);
    return 4 + fbe_string_size;
}

size_t FinalModel<std::string>::set(const std::string& value)
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    uint32_t fbe_string_size = (uint32_t)value.size();
    assert(((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4 + fbe_string_size) > _buffer.size())
        return 4;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_string_size;

    memcpy((char*)(_buffer.data() + _buffer.offset() + fbe_offset() + 4), value.data(), fbe_string_size);
    return 4 + fbe_string_size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelOptional_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model optional specialization
template <typename T>
class FinalModel<std::optional<T>>
{
public:
    FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset), value(buffer, 0) {}

    // Get the allocation size
    size_t fbe_allocation_size(const std::optional<T>& opt) const noexcept { return 1 + (opt ? value.fbe_allocation_size(opt.value()) : 0); }

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    //! Is the value present?
    explicit operator bool() const noexcept { return has_value(); }

    // Checks if the object contains a value
    bool has_value() const noexcept;

    // Check if the optional value is valid
    size_t verify() const noexcept;

    // Get the optional value
    size_t get(std::optional<T>& opt) const noexcept;
    // Set the optional value
    size_t set(const std::optional<T>& opt);

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;

public:
    // Base final model value
    FinalModel<T> value;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelOptional_Inline()
{
    std::string code = R"CODE(
template <typename T>
inline bool FinalModel<std::optional<T>>::has_value() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 1) > _buffer.size())
        return false;

    uint8_t fbe_has_value = *((const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    return (fbe_has_value != 0);
}

template <typename T>
inline size_t FinalModel<std::optional<T>>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 1) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    uint8_t fbe_has_value = *((const uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_has_value == 0)
        return 1;

    _buffer.shift(fbe_offset() + 1);
    size_t fbe_result = value.verify();
    _buffer.unshift(fbe_offset() + 1);
    return 1 + fbe_result;
}

template <typename T>
inline size_t FinalModel<std::optional<T>>::get(std::optional<T>& opt) const noexcept
{
    opt = std::nullopt;

    assert(((_buffer.offset() + fbe_offset() + 1) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 1) > _buffer.size())
        return 0;

    if (!has_value())
        return 1;

    _buffer.shift(fbe_offset() + 1);
    T temp = T();
    size_t size = value.get(temp);
    opt.emplace(temp);
    _buffer.unshift(fbe_offset() + 1);
    return 1 + size;
}

template <typename T>
inline size_t FinalModel<std::optional<T>>::set(const std::optional<T>& opt)
{
    assert(((_buffer.offset() + fbe_offset() + 1) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 1) > _buffer.size())
        return 0;

    uint8_t fbe_has_value = opt ? 1 : 0;
    *((uint8_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_has_value;
    if (fbe_has_value == 0)
        return 1;

    _buffer.shift(fbe_offset() + 1);
    size_t size = 0;
    if (opt)
        size = value.set(opt.value());
    _buffer.unshift(fbe_offset() + 1);
    return 1 + size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelArray_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model array
template <typename T, size_t N>
class FinalModelArray
{
public:
    FinalModelArray(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    template <size_t S>
    size_t fbe_allocation_size(const T (&values)[S]) const noexcept;
    template <size_t S>
    size_t fbe_allocation_size(const std::array<T, S>& values) const noexcept;
    size_t fbe_allocation_size(const std::vector<T>& values) const noexcept;

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the array is valid
    size_t verify() const noexcept;

    // Get the array as C-array
    template <size_t S>
    size_t get(T (&values)[S]) const noexcept;
    // Get the array as std::array
    template <size_t S>
    size_t get(std::array<T, S>& values) const noexcept;
    // Get the array as std::vector
    size_t get(std::vector<T>& values) const noexcept;

    // Set the array as C-array
    template <size_t S>
    size_t set(const T (&values)[S]) noexcept;
    // Set the array as std::array
    template <size_t S>
    size_t set(const std::array<T, S>& values) noexcept;
    // Set the array as std::vector
    size_t set(const std::vector<T>& values) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelArray_Inline()
{
    std::string code = R"CODE(
template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::fbe_allocation_size(const T (&values)[S]) const noexcept
{
    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
        size += fbe_model.fbe_allocation_size(values[i]);
    return size;
}

template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::fbe_allocation_size(const std::array<T, S>& values) const noexcept
{
    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
        size += fbe_model.fbe_allocation_size(values[i]);
    return size;
}

template <typename T, size_t N>
inline size_t FinalModelArray<T, N>::fbe_allocation_size(const std::vector<T>& values) const noexcept
{
    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < values.size()) && (i < N); ++i)
        size += fbe_model.fbe_allocation_size(values[i]);
    return size;
}

template <typename T, size_t N>
inline size_t FinalModelArray<T, N>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = N; i-- > 0;)
    {
        size_t offset = fbe_model.verify();
        if (offset == std::numeric_limits<std::size_t>::max())
            return std::numeric_limits<std::size_t>::max();
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::get(T (&values)[S]) const noexcept
{
    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        size_t offset = fbe_model.get(values[i]);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::get(std::array<T, S>& values) const noexcept
{
    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        size_t offset = fbe_model.get(values[i]);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
inline size_t FinalModelArray<T, N>::get(std::vector<T>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    values.reserve(N);

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = N; i-- > 0;)
    {
        T value = T();
        size_t offset = fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::set(const T (&values)[S]) noexcept
{
    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        size_t offset = fbe_model.set(values[i]);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
template <size_t S>
inline size_t FinalModelArray<T, N>::set(const std::array<T, S>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < S) && (i < N); ++i)
    {
        size_t offset = fbe_model.set(values[i]);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T, size_t N>
inline size_t FinalModelArray<T, N>::set(const std::vector<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset()) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset()) > _buffer.size())
        return 0;

    size_t size = 0;
    FinalModel<T> fbe_model(_buffer, fbe_offset());
    for (size_t i = 0; (i < values.size()) && (i < N); ++i)
    {
        size_t offset = fbe_model.set(values[i]);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelVector_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model vector
template <typename T>
class FinalModelVector
{
public:
    FinalModelVector(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(const std::vector<T>& values) const noexcept;
    size_t fbe_allocation_size(const std::list<T>& values) const noexcept;
    size_t fbe_allocation_size(const std::set<T>& values) const noexcept;

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the vector is valid
    size_t verify() const noexcept;

    // Get the vector as std::vector
    size_t get(std::vector<T>& values) const noexcept;
    // Get the vector as std::list
    size_t get(std::list<T>& values) const noexcept;
    // Get the vector as std::set
    size_t get(std::set<T>& values) const noexcept;

    // Set the vector as std::vector
    size_t set(const std::vector<T>& values) noexcept;
    // Set the vector as std::list
    size_t set(const std::list<T>& values) noexcept;
    // Set the vector as std::set
    size_t set(const std::set<T>& values) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelVector_Inline()
{
    std::string code = R"CODE(
template <typename T>
inline size_t FinalModelVector<T>::fbe_allocation_size(const std::vector<T>& values) const noexcept
{
    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
        size += fbe_model.fbe_allocation_size(value);
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::fbe_allocation_size(const std::list<T>& values) const noexcept
{
    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
        size += fbe_model.fbe_allocation_size(value);
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::fbe_allocation_size(const std::set<T>& values) const noexcept
{
    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
        size += fbe_model.fbe_allocation_size(value);
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    uint32_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        size_t offset = fbe_model.verify();
        if (offset == std::numeric_limits<std::size_t>::max())
            return std::numeric_limits<std::size_t>::max();
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::get(std::vector<T>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    size_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_vector_size == 0)
        return 4;

    values.reserve(fbe_vector_size);

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        size_t offset = fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::get(std::list<T>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    size_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_vector_size == 0)
        return 4;

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        size_t offset = fbe_model.get(value);
        values.emplace_back(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::get(std::set<T>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    size_t fbe_vector_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_vector_size == 0)
        return 4;

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_vector_size; i-- > 0;)
    {
        T value = T();
        size_t offset = fbe_model.get(value);
        values.emplace(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::set(const std::vector<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)values.size();

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size_t offset = fbe_model.set(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::set(const std::list<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)values.size();

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size_t offset = fbe_model.set(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}

template <typename T>
inline size_t FinalModelVector<T>::set(const std::set<T>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)values.size();

    size_t size = 4;
    FinalModel<T> fbe_model(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size_t offset = fbe_model.set(value);
        fbe_model.fbe_shift(offset);
        size += offset;
    }
    return size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelMap_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding final model map
template <typename TKey, typename TValue>
class FinalModelMap
{
public:
    FinalModelMap(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset) {}

    // Get the allocation size
    size_t fbe_allocation_size(const std::map<TKey, TValue>& values) const noexcept;
    size_t fbe_allocation_size(const std::unordered_map<TKey, TValue>& values) const noexcept;

    // Get the field offset
    size_t fbe_offset() const noexcept { return _offset; }
    // Set the field offset
    size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }

    // Shift the current field offset
    void fbe_shift(size_t size) noexcept { _offset += size; }
    // Unshift the current field offset
    void fbe_unshift(size_t size) noexcept { _offset -= size; }

    // Check if the map is valid
    size_t verify() const noexcept;

    // Get the map as std::map
    size_t get(std::map<TKey, TValue>& values) const noexcept;
    // Get the map as std::unordered_map
    size_t get(std::unordered_map<TKey, TValue>& values) const noexcept;

    // Set the map as std::map
    size_t set(const std::map<TKey, TValue>& values) noexcept;
    // Set the map as std::unordered_map
    size_t set(const std::unordered_map<TKey, TValue>& values) noexcept;

private:
    FBEBuffer& _buffer;
    mutable size_t _offset;
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEFinalModelMap_Inline()
{
    std::string code = R"CODE(
template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::fbe_allocation_size(const std::map<TKey, TValue>& values) const noexcept
{
    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size += fbe_model_key.fbe_allocation_size(value.first);
        size += fbe_model_value.fbe_allocation_size(value.second);
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::fbe_allocation_size(const std::unordered_map<TKey, TValue>& values) const noexcept
{
    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size += fbe_model_key.fbe_allocation_size(value.first);
        size += fbe_model_value.fbe_allocation_size(value.second);
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::verify() const noexcept
{
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return std::numeric_limits<std::size_t>::max();

    uint32_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));

    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        size_t offset_key = fbe_model_key.verify();
        if (offset_key == std::numeric_limits<std::size_t>::max())
            return std::numeric_limits<std::size_t>::max();
        fbe_model_key.fbe_shift(offset_key);
        fbe_model_value.fbe_shift(offset_key);
        size += offset_key;
        size_t offset_value = fbe_model_value.verify();
        if (offset_value == std::numeric_limits<std::size_t>::max())
            return std::numeric_limits<std::size_t>::max();
        fbe_model_key.fbe_shift(offset_value);
        fbe_model_value.fbe_shift(offset_value);
        size += offset_value;
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::get(std::map<TKey, TValue>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    size_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_map_size == 0)
        return 4;

    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        TKey key = TKey();
        TValue value = TValue();
        size_t offset_key = fbe_model_key.get(key);
        fbe_model_key.fbe_shift(offset_key);
        fbe_model_value.fbe_shift(offset_key);
        size_t offset_value = fbe_model_value.get(value);
        fbe_model_key.fbe_shift(offset_value);
        fbe_model_value.fbe_shift(offset_value);
        values.emplace(key, value);
        size += offset_key + offset_value;
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::get(std::unordered_map<TKey, TValue>& values) const noexcept
{
    values.clear();

    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    size_t fbe_map_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));
    if (fbe_map_size == 0)
        return 4;

    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (size_t i = fbe_map_size; i-- > 0;)
    {
        TKey key = TKey();
        TValue value = TValue();
        size_t offset_key = fbe_model_key.get(key);
        fbe_model_key.fbe_shift(offset_key);
        fbe_model_value.fbe_shift(offset_key);
        size_t offset_value = fbe_model_value.get(value);
        fbe_model_key.fbe_shift(offset_value);
        fbe_model_value.fbe_shift(offset_value);
        values.emplace(key, value);
        size += offset_key + offset_value;
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::set(const std::map<TKey, TValue>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)values.size();

    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size_t offset_key = fbe_model_key.set(value.first);
        fbe_model_key.fbe_shift(offset_key);
        fbe_model_value.fbe_shift(offset_key);
        size_t offset_value = fbe_model_value.set(value.second);
        fbe_model_key.fbe_shift(offset_value);
        fbe_model_value.fbe_shift(offset_value);
        size += offset_key + offset_value;
    }
    return size;
}

template <typename TKey, typename TValue>
inline size_t FinalModelMap<TKey, TValue>::set(const std::unordered_map<TKey, TValue>& values) noexcept
{
    assert(((_buffer.offset() + fbe_offset() + 4) <= _buffer.size()) && "Model is broken!");
    if ((_buffer.offset() + fbe_offset() + 4) > _buffer.size())
        return 0;

    *((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = (uint32_t)values.size();

    size_t size = 4;
    FinalModel<TKey> fbe_model_key(_buffer, fbe_offset() + 4);
    FinalModel<TValue> fbe_model_value(_buffer, fbe_offset() + 4);
    for (const auto& value : values)
    {
        size_t offset_key = fbe_model_key.set(value.first);
        fbe_model_key.fbe_shift(offset_key);
        fbe_model_value.fbe_shift(offset_key);
        size_t offset_value = fbe_model_value.set(value.second);
        fbe_model_key.fbe_shift(offset_value);
        fbe_model_value.fbe_shift(offset_value);
        size += offset_key + offset_value;
    }
    return size;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBESender_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding base sender
class Sender
{
public:
    Sender(const Sender&) = delete;
    Sender(Sender&&) noexcept = delete;
    virtual ~Sender() = default;

    Sender& operator=(const Sender&) = delete;
    Sender& operator=(Sender&&) noexcept = delete;

    // Get the sender buffer
    FBEBuffer& buffer() noexcept { return *_buffer; }
    const FBEBuffer& buffer() const noexcept { return *_buffer; }

    // Get the final protocol flag
    bool final() const noexcept { return _final; }

    // Get the logging flag
    bool logging() const noexcept { return _logging; }
    // Enable/Disable logging
    void logging(bool enable) noexcept { _logging = enable; }

    // Reset the sender buffer
    void reset() noexcept { _buffer->reset(); }

    // Send serialized buffer.
    // Direct call of the method requires knowledge about internals of FBE models serialization.
    // Use it with care!
    size_t send_serialized(size_t serialized);

protected:
    // Send message handler
    virtual size_t onSend(const void* data, size_t size) = 0;
    // Send log message handler
    virtual void onSendLog(const std::string& message) const {}

protected:
    std::shared_ptr<FBEBuffer> _buffer;
    bool _logging;
    bool _final;

    Sender() : Sender(nullptr) {}
    Sender(const std::shared_ptr<FBEBuffer>& buffer) : _logging(false), _final(false) { _buffer = buffer ? buffer : std::make_shared<FBEBuffer>(); }

    // Enable/Disable final protocol
    void final(bool enable) noexcept { _final = enable; }
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBESender_Source()
{
    std::string code = R"CODE(
size_t Sender::send_serialized(size_t serialized)
{
    assert((serialized > 0) && "Invalid size of the serialized buffer!");
    if (serialized == 0)
        return 0;

    // Shift the send buffer
    this->_buffer->shift(serialized);

    // Send the value
    size_t sent = onSend(this->_buffer->data(), this->_buffer->size());
    this->_buffer->remove(0, sent);
    return sent;
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEReceiver_Header()
{
    std::string code = R"CODE(
// Fast Binary Encoding base receiver
class Receiver
{
public:
    Receiver(const Receiver&) = delete;
    Receiver(Receiver&&) = delete;
    virtual ~Receiver() = default;

    Receiver& operator=(const Receiver&) = delete;
    Receiver& operator=(Receiver&&) = delete;

    // Get the receiver buffer
    FBEBuffer& buffer() noexcept { return *_buffer; }
    const FBEBuffer& buffer() const noexcept { return *_buffer; }

    // Get the final protocol flag
    bool final() const noexcept { return _final; }

    // Get the logging flag
    bool logging() const noexcept { return _logging; }
    // Enable/Disable logging
    void logging(bool enable) noexcept { _logging = enable; }

    // Reset the receiver buffer
    void reset() noexcept { _buffer->reset(); }

    // Receive data
    void receive(const void* data, size_t size);

protected:
    // Receive message handler
    virtual bool onReceive(size_t type, const void* data, size_t size) = 0;
    // Receive log message handler
    virtual void onReceiveLog(const std::string& message) const {}

protected:
    std::shared_ptr<FBEBuffer> _buffer;
    bool _logging;
    bool _final;

    Receiver() : Receiver(nullptr) {}
    Receiver(const std::shared_ptr<FBEBuffer>& buffer) : _logging(false), _final(false) { _buffer = buffer ? buffer : std::make_shared<FBEBuffer>(); }

    // Enable/Disable final protocol
    void final(bool enable) noexcept { _final = enable; }
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEReceiver_Source()
{
    std::string code = R"CODE(
void Receiver::receive(const void* data, size_t size)
{
    if (size == 0)
        return;

    assert((data != nullptr) && "Invalid buffer!");
    if (data == nullptr)
        return;

    // Storage buffer
    uint8_t* buffer1 = _buffer->data();
    size_t offset0 = _buffer->offset();
    size_t offset1 = _buffer->size();
    size_t size1 = _buffer->size();

    // Receive buffer
    const uint8_t* buffer2 = (const uint8_t*)data;
    size_t offset2 = 0;
    size_t size2 = size;

    // While receive buffer is available to handle...
    while (offset2 < size2)
    {
        const uint8_t* message_buffer = nullptr;
        size_t message_size = 0;

        // Try to receive message size
        bool message_size_copied = false;
        bool message_size_found = false;
        while (!message_size_found)
        {
            // Look into the storage buffer
            if (offset0 < size1)
            {
                size_t count = std::min(size1 - offset0, (size_t)4);
                if (count == 4)
                {
                    message_size_copied = true;
                    message_size_found = true;
                    message_size = (size_t)(*((const uint32_t*)(buffer1 + offset0)));
                    offset0 += 4;
                    break;
                }
                else
                {
                    // Fill remaining data from the receive buffer
                    if (offset2 < size2)
                    {
                        count = std::min(size2 - offset2, 4 - count);

                        // Allocate and refresh the storage buffer
                        _buffer->allocate(count);
                        buffer1 = _buffer->data();
                        size1 += count;

                        memcpy(buffer1 + offset1, buffer2 + offset2, count);
                        offset1 += count;
                        offset2 += count;
                        continue;
                    }
                    else
                        break;
                }
            }

            // Look into the receive buffer
            if (offset2 < size2)
            {
                size_t count = std::min(size2 - offset2, (size_t)4);
                if (count == 4)
                {
                    message_size_found = true;
                    message_size = (size_t)(*((const uint32_t*)(buffer2 + offset2)));
                    offset2 += 4;
                    break;
                }
                else
                {
                    // Allocate and refresh the storage buffer
                    _buffer->allocate(count);
                    buffer1 = _buffer->data();
                    size1 += count;

                    memcpy(buffer1 + offset1, buffer2 + offset2, count);
                    offset1 += count;
                    offset2 += count;
                    continue;
                }
            }
            else
                break;
        }

        if (!message_size_found)
            return;

        // Check the message full size
        size_t min_size = _final ? (4 + 4) : (4 + 4 + 4 + 4);
        assert((message_size >= min_size) && "Invalid receive data!");
        if (message_size < min_size)
            return;

        // Try to receive message body
        bool message_found = false;
        while (!message_found)
        {
            // Look into the storage buffer
            if (offset0 < size1)
            {
                size_t count = std::min(size1 - offset0, message_size - 4);
                if (count == (message_size - 4))
                {
                    message_found = true;
                    message_buffer = buffer1 + offset0 - 4;
                    offset0 += message_size - 4;
                    break;
                }
                else
                {
                    // Fill remaining data from the receive buffer
                    if (offset2 < size2)
                    {
                        // Copy message size into the storage buffer
                        if (!message_size_copied)
                        {
                            // Allocate and refresh the storage buffer
                            _buffer->allocate(4);
                            buffer1 = _buffer->data();
                            size1 += 4;

                            *((uint32_t*)(buffer1 + offset0)) = (uint32_t)message_size;
                            offset0 += 4;
                            offset1 += 4;

                            message_size_copied = true;
                        }

                        count = std::min(size2 - offset2, message_size - 4 - count);

                        // Allocate and refresh the storage buffer
                        _buffer->allocate(count);
                        buffer1 = _buffer->data();
                        size1 += count;

                        memcpy(buffer1 + offset1, buffer2 + offset2, count);
                        offset1 += count;
                        offset2 += count;
                        continue;
                    }
                    else
                        break;
                }
            }

            // Look into the receive buffer
            if (offset2 < size2)
            {
                size_t count = std::min(size2 - offset2, message_size - 4);
                if (!message_size_copied && (count == (message_size - 4)))
                {
                    message_found = true;
                    message_buffer = buffer2 + offset2 - 4;
                    offset2 += message_size - 4;
                    break;
                }
                else
                {
                    // Copy message size into the storage buffer
                    if (!message_size_copied)
                    {
                        // Allocate and refresh the storage buffer
                        _buffer->allocate(4);
                        buffer1 = _buffer->data();
                        size1 += 4;

                        *((uint32_t*)(buffer1 + offset0)) = (uint32_t)message_size;
                        offset0 += 4;
                        offset1 += 4;

                        message_size_copied = true;
                    }

                    // Allocate and refresh the storage buffer
                    _buffer->allocate(count);
                    buffer1 = _buffer->data();
                    size1 += count;

                    memcpy(buffer1 + offset1, buffer2 + offset2, count);
                    offset1 += count;
                    offset2 += count;
                    continue;
                }
            }
            else
                break;
        }

        if (!message_found)
        {
            // Copy message size into the storage buffer
            if (!message_size_copied)
            {
                // Allocate and refresh the storage buffer
                _buffer->allocate(4);
                buffer1 = _buffer->data();
                size1 += 4;

                *((uint32_t*)(buffer1 + offset0)) = (uint32_t)message_size;
                offset0 += 4;
                offset1 += 4;

                message_size_copied = true;
            }
            return;
        }

        [[maybe_unused]] uint32_t fbe_struct_size;
        uint32_t fbe_struct_type;

        // Read the message parameters
        if (_final)
        {
            fbe_struct_size = *((const uint32_t*)(message_buffer));
            fbe_struct_type = *((const uint32_t*)(message_buffer + 4));
        }
        else
        {
            uint32_t fbe_struct_offset = *((const uint32_t*)(message_buffer + 4));
            fbe_struct_size = *((const uint32_t*)(message_buffer + fbe_struct_offset));
            fbe_struct_type = *((const uint32_t*)(message_buffer + fbe_struct_offset + 4));
        }

        // Handle the message
        onReceive(fbe_struct_type, message_buffer, message_size);

        // Reset the storage buffer
        _buffer->reset();

        // Refresh the storage buffer
        buffer1 = _buffer->data();
        offset0 = _buffer->offset();
        offset1 = _buffer->size();
        size1 = _buffer->size();
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorCpp::GenerateFBEJson()
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace JSON {");

    std::string code = R"CODE(
template <class TWriter, typename T>
struct KeyWriter
{
    static bool to_json_key(TWriter& writer, const T& key)
    {
        return writer.Key(std::to_string(key));
    }
};

template <class TWriter, typename T>
bool to_json_key(TWriter& writer, const T& key)
{
    return KeyWriter<TWriter, T>::to_json_key(writer, key);
}

template <class TWriter>
struct KeyWriter<TWriter, FBE::decimal_t>
{
    static bool to_json_key(TWriter& writer, const FBE::decimal_t& key)
    {
        return writer.Key(key.string());
    }
};

template <class TWriter>
struct KeyWriter<TWriter, FBE::uuid_t>
{
    static bool to_json_key(TWriter& writer, const FBE::uuid_t& key)
    {
        return writer.Key(key.string());
    }
};

template <class TWriter>
struct KeyWriter<TWriter, std::string>
{
    static bool to_json_key(TWriter& writer, const std::string& key)
    {
        return writer.Key(key);
    }
};

template <class TWriter, size_t N>
struct KeyWriter<TWriter, char[N]>
{
    static bool to_json_key(TWriter& writer, const char (&key)[N])
    {
        return writer.Key(key, N - 1);
    }
};

template <class TWriter, typename T>
struct ValueWriter
{
    static bool to_json(TWriter& writer, const T& value, bool scope = true)
    {
        throw std::logic_error("Not implemented!");
    }
};

template <class TWriter, typename T>
bool to_json(TWriter& writer, const T& value, bool scope = true)
{
    return ValueWriter<TWriter, T>::to_json(writer, value, scope);
}

template <class TWriter>
struct ValueWriter<TWriter, bool>
{
    static bool to_json(TWriter& writer, const bool& value, bool scope = true)
    {
        return writer.Bool(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, char>
{
    static bool to_json(TWriter& writer, const char& value, bool scope = true)
    {
        return writer.Uint(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, wchar_t>
{
    static bool to_json(TWriter& writer, const wchar_t& value, bool scope = true)
    {
        return writer.Uint(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, int8_t>
{
    static bool to_json(TWriter& writer, const int8_t& value, bool scope = true)
    {
        return writer.Int(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, uint8_t>
{
    static bool to_json(TWriter& writer, const uint8_t& value, bool scope = true)
    {
        return writer.Uint(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, int16_t>
{
    static bool to_json(TWriter& writer, const int16_t& value, bool scope = true)
    {
        return writer.Int(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, uint16_t>
{
    static bool to_json(TWriter& writer, const uint16_t& value, bool scope = true)
    {
        return writer.Uint(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, int32_t>
{
    static bool to_json(TWriter& writer, const int32_t& value, bool scope = true)
    {
        return writer.Int(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, uint32_t>
{
    static bool to_json(TWriter& writer, const uint32_t& value, bool scope = true)
    {
        return writer.Uint(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, int64_t>
{
    static bool to_json(TWriter& writer, const int64_t& value, bool scope = true)
    {
        return writer.Int64(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, uint64_t>
{
    static bool to_json(TWriter& writer, const uint64_t& value, bool scope = true)
    {
        return writer.Uint64(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, float>
{
    static bool to_json(TWriter& writer, const float& value, bool scope = true)
    {
        return writer.Double(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, double>
{
    static bool to_json(TWriter& writer, const double& value, bool scope = true)
    {
        return writer.Double(value);
    }
};

template <class TWriter>
struct ValueWriter<TWriter, FBE::decimal_t>
{
    static bool to_json(TWriter& writer, const FBE::decimal_t& value, bool scope = true)
    {
        return writer.String(value.string());
    }
};

template <class TWriter>
struct ValueWriter<TWriter, FBE::uuid_t>
{
    static bool to_json(TWriter& writer, const FBE::uuid_t& value, bool scope = true)
    {
        return writer.String(value.string());
    }
};

template <class TWriter>
struct ValueWriter<TWriter, std::string>
{
    static bool to_json(TWriter& writer, const std::string& value, bool scope = true)
    {
        return writer.String(value);
    }
};

template <class TWriter, std::size_t N>
struct ValueWriter<TWriter, char[N]>
{
    static bool to_json(TWriter& writer, const char (&value)[N], bool scope = true)
    {
        return writer.String(value, N);
    }
};

template <class TWriter, typename T>
struct ValueWriter<TWriter, std::optional<T>>
{
    static bool to_json(TWriter& writer, const std::optional<T>& value, bool scope = true)
    {
        if (value)
            return ValueWriter<TWriter, T>::to_json(writer, value.value(), true);
        else
            return writer.Null();
    }
};

template <class TWriter>
struct ValueWriter<TWriter, buffer_t>
{
    static bool to_json(TWriter& writer, const buffer_t& values, bool scope = true)
    {
        return writer.String(values.base64encode());
    }
};

template <class TWriter, typename T, size_t N>
struct ValueWriter<TWriter, std::array<T, N>>
{
    static bool to_json(TWriter& writer, const std::array<T, N>& values, bool scope = true)
    {
        writer.StartArray();
        for (const auto& value : values)
            if (!ValueWriter<TWriter, T>::to_json(writer, value, true))
                return false;
        writer.EndArray();
        return true;
    }
};

template <class TWriter, typename T>
struct ValueWriter<TWriter, std::vector<T>>
{
    static bool to_json(TWriter& writer, const std::vector<T>& values, bool scope = true)
    {
        writer.StartArray();
        for (const auto& value : values)
            if (!FBE::JSON::to_json(writer, value, true))
                return false;
        writer.EndArray();
        return true;
    }
};

template <class TWriter, typename T>
struct ValueWriter<TWriter, std::list<T>>
{
    static bool to_json(TWriter& writer, const std::list<T>& values, bool scope = true)
    {
        writer.StartArray();
        for (const auto& value : values)
            if (!FBE::JSON::to_json(writer, value, true))
                return false;
        writer.EndArray();
        return true;
    }
};

template <class TWriter, typename T>
struct ValueWriter<TWriter, std::set<T>>
{
    static bool to_json(TWriter& writer, const std::set<T>& values, bool scope = true)
    {
        writer.StartArray();
        for (const auto& value : values)
            if (!FBE::JSON::to_json(writer, value, true))
                return false;
        writer.EndArray();
        return true;
    }
};

template <class TWriter, typename TKey, typename TValue>
struct ValueWriter<TWriter, std::map<TKey, TValue>>
{
    static bool to_json(TWriter& writer, const std::map<TKey, TValue>& values, bool scope = true)
    {
        writer.StartObject();
        for (const auto& value : values)
        {
            if (!FBE::JSON::to_json_key(writer, value.first))
                return false;
            if (!FBE::JSON::to_json(writer, value.second, true))
                return false;
        }
        writer.EndObject();
        return true;
    }
};

template <class TWriter, typename TKey, typename TValue>
struct ValueWriter<TWriter, std::unordered_map<TKey, TValue>>
{
    static bool to_json(TWriter& writer, const std::unordered_map<TKey, TValue>& values, bool scope = true)
    {
        writer.StartObject();
        for (const auto& value : values)
        {
            if (!FBE::JSON::to_json_key(writer, value.first))
                return false;
            if (!FBE::JSON::to_json(writer, value.second, true))
                return false;
        }
        writer.EndObject();
        return true;
    }
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
template <class TJson, typename T>
struct ValueReader
{
    static bool from_json(const TJson& json, T& value)
    {
        throw std::logic_error("Not implemented!");
    }
};

template <class TJson, typename T>
bool from_json(const TJson& json, T& value)
{
    return ValueReader<TJson, T>::from_json(json, value);
}

template <class TJson, typename T>
struct KeyReader
{
    static bool from_json_key(const TJson& json, T& key)
    {
        std::string str;
        if (!FBE::JSON::from_json(json, str))
            return false;

        std::istringstream(str) >> key;
        return true;
    }
};

template <class TJson, typename T>
bool from_json_key(const TJson& json, T& key)
{
    return KeyReader<TJson, T>::from_json_key(json, key);
}

template <class TJson>
struct KeyReader<TJson, std::string>
{
    static bool from_json_key(const TJson& json, std::string& key)
    {
        return FBE::JSON::from_json(json, key);
    }
};

template <class TJson>
struct KeyReader<TJson, FBE::decimal_t>
{
    static bool from_json_key(const TJson& json, FBE::decimal_t& key)
    {
        return FBE::JSON::from_json(json, key);
    }
};

template <class TJson>
struct KeyReader<TJson, FBE::uuid_t>
{
    static bool from_json_key(const TJson& json, FBE::uuid_t& key)
    {
        return FBE::JSON::from_json(json, key);
    }
};

template <class TJson, size_t N>
struct KeyReader<TJson, char[N]>
{
    static bool from_json_key(const TJson& json, char (&key)[N])
    {
        return FBE::JSON::from_json(json, key);
    }
};

template <class TJson>
struct ValueReader<TJson, bool>
{
    static bool from_json(const TJson& json, bool& value)
    {
        value = false;

        // Schema validation
        if (json.IsNull() || !json.IsBool())
            return false;

        // Save the value
        value = json.GetBool();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, char>
{
    static bool from_json(const TJson& json, char& value)
    {
        value = '\0';

        // Schema validation
        if (json.IsNull() || !json.IsUint())
            return false;

        // Save the value
        value = (char)json.GetUint();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, wchar_t>
{
    static bool from_json(const TJson& json, wchar_t& value)
    {
        value = L'\0';

        // Schema validation
        if (json.IsNull() || !json.IsUint())
            return false;

        // Save the value
        value = (wchar_t)json.GetUint();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, int8_t>
{
    static bool from_json(const TJson& json, int8_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsInt())
            return false;

        // Save the value
        value = (int8_t)json.GetInt();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, uint8_t>
{
    static bool from_json(const TJson& json, uint8_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsUint())
            return false;

        // Save the value
        value = (uint8_t)json.GetUint();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, int16_t>
{
    static bool from_json(const TJson& json, int16_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsInt())
            return false;

        // Save the value
        value = (int16_t)json.GetInt();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, uint16_t>
{
    static bool from_json(const TJson& json, uint16_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsUint())
            return false;

        // Save the value
        value = (uint16_t)json.GetUint();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, int32_t>
{
    static bool from_json(const TJson& json, int32_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsInt())
            return false;

        // Save the value
        value = (int32_t)json.GetInt();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, uint32_t>
{
    static bool from_json(const TJson& json, uint32_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsUint())
            return false;

        // Save the value
        value = (uint32_t)json.GetUint();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, int64_t>
{
    static bool from_json(const TJson& json, int64_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsInt64())
            return false;

        // Save the value
        value = (int64_t)json.GetInt64();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, uint64_t>
{
    static bool from_json(const TJson& json, uint64_t& value)
    {
        value = 0;

        // Schema validation
        if (json.IsNull() || !json.IsUint64())
            return false;

        // Save the value
        value = (uint64_t)json.GetUint64();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, float>
{
    static bool from_json(const TJson& json, float& value)
    {
        value = 0.0f;

        // Schema validation
        if (json.IsNull() || !(json.IsInt() || json.IsFloat()))
            return false;

        // Save the value
        value = json.GetFloat();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, double>
{
    static bool from_json(const TJson& json, double& value)
    {
        value = 0.0;

        // Schema validation
        if (json.IsNull() || !(json.IsInt() || json.IsDouble()))
            return false;

        // Save the value
        value = json.GetDouble();
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, FBE::decimal_t>
{
    static bool from_json(const TJson& json, FBE::decimal_t& value)
    {
        value = 0.0;

        // Schema validation
        if (json.IsNull() || !json.IsString())
            return false;

        // Save the value
        try
        {
            std::string str(json.GetString(), (size_t)json.GetStringLength());
            value = std::stod(str);
            return true;
        }
        catch (...)
        {
            return false;
        }
    }
};

template <class TJson>
struct ValueReader<TJson, FBE::uuid_t>
{
    static bool from_json(const TJson& json, FBE::uuid_t& value)
    {
        value = uuid_t::nil();

        // Schema validation
        if (json.IsNull() || !json.IsString())
            return false;

        // Save the value
        try
        {
            std::string str(json.GetString(), (size_t)json.GetStringLength());
            value = str;
            return true;
        }
        catch (...)
        {
            return false;
        }
    }
};

template <class TJson>
struct ValueReader<TJson, std::string>
{
    static bool from_json(const TJson& json, std::string& value)
    {
        value = "";

        // Schema validation
        if (json.IsNull() || !json.IsString())
            return false;

        // Save the value
        value.assign(json.GetString(), (size_t)json.GetStringLength());
        return true;
    }
};

template <class TJson, size_t N>
struct ValueReader<TJson, char[N]>
{
    static bool from_json(const TJson& json, char (&value)[N])
    {
        // Schema validation
        if (json.IsNull() || !json.IsString())
            return false;

        // Save the value
        size_t length = std::min((size_t)json.GetStringLength(), N);
        std::memcpy(value, json.GetString(), length);
        // Write the end of string character if possible
        if (length < N)
            value[length] = '\0';
        return true;
    }
};

template <class TJson, typename T>
struct ValueReader<TJson, std::optional<T>>
{
    static bool from_json(const TJson& json, std::optional<T>& value)
    {
        value = std::nullopt;

        // Empty optional value
        if (json.IsNull())
            return true;

        // Try to get the value
        T temp = T();
        if (!FBE::JSON::from_json(json, temp))
            return false;

        // Save the value
        value.emplace(temp);
        return true;
    }
};

template <class TJson>
struct ValueReader<TJson, buffer_t>
{
    static bool from_json(const TJson& json, buffer_t& value)
    {
        // Schema validation
        if (json.IsNull() || !json.IsString())
            return false;

        std::string str(json.GetString(), (size_t)json.GetStringLength());
        value = buffer_t::base64decode(str);
        return true;
    }
};

template <class TJson, typename T, size_t N>
struct ValueReader<TJson, std::array<T, N>>
{
    static bool from_json(const TJson& json, std::array<T, N>& values)
    {
        // Schema validation
        if (json.IsNull() || !json.IsArray())
            return false;

        // Collect array items
        size_t length = json.GetArray().Size();
        for (size_t i = 0; (i < length) && (i < N); ++i)
            if (!FBE::JSON::from_json(json.GetArray()[(rapidjson::SizeType)i], values[i]))
                return false;
        return true;
    }
};

template <class TJson, typename T>
struct ValueReader<TJson, std::vector<T>>
{
    static bool from_json(const TJson& json, std::vector<T>& values)
    {
        values.clear();

        // Schema validation
        if (json.IsNull() || !json.IsArray())
            return false;

        // Collect vector items
        values.reserve(json.GetArray().Size());
        for (const auto& item : json.GetArray())
        {
            T temp = T();
            if (!FBE::JSON::from_json(item, temp))
                return false;
            values.emplace_back(temp);
        }
        return true;
    }
};

template <class TJson, typename T>
struct ValueReader<TJson, std::list<T>>
{
    static bool from_json(const TJson& json, std::list<T>& values)
    {
        values.clear();

        // Schema validation
        if (json.IsNull() || !json.IsArray())
            return false;

        // Collect list items
        for (const auto& item : json.GetArray())
        {
            T temp = T();
            if (!FBE::JSON::from_json(item, temp))
                return false;
            values.emplace_back(temp);
        }
        return true;
    }
};

template <class TJson, typename T>
struct ValueReader<TJson, std::set<T>>
{
    static bool from_json(const TJson& json, std::set<T>& values)
    {
        values.clear();

        // Schema validation
        if (json.IsNull() || !json.IsArray())
            return false;

        // Collect set items
        for (const auto& item : json.GetArray())
        {
            T temp = T();
            if (!FBE::JSON::from_json(item, temp))
                return false;
            values.emplace(temp);
        }
        return true;
    }
};

template <class TJson, typename TKey, typename TValue>
struct ValueReader<TJson, std::map<TKey, TValue>>
{
    static bool from_json(const TJson& json, std::map<TKey, TValue>& values)
    {
        values.clear();

        // Schema validation
        if (json.IsNull() || !json.IsObject())
            return false;

        // Collect map items
        for (auto it = json.MemberBegin(); it != json.MemberEnd(); ++it)
        {
            TKey key = TKey();
            TValue value = TValue();
            if (!FBE::JSON::from_json_key(it->name, key))
                return false;
            if (!FBE::JSON::from_json(it->value, value))
                return false;
            values.emplace(key, value);
        }
        return true;
    }
};

template <class TJson, typename TKey, typename TValue>
struct ValueReader<TJson, std::unordered_map<TKey, TValue>>
{
    static bool from_json(const TJson& json, std::unordered_map<TKey, TValue>& values)
    {
        values.clear();

        // Schema validation
        if (json.IsNull() || !json.IsObject())
            return false;

        // Collect hash items
        for (auto it = json.MemberBegin(); it != json.MemberEnd(); ++it)
        {
            TKey key = TKey();
            TValue value = TValue();
            if (!FBE::JSON::from_json_key(it->name, key))
                return false;
            if (!FBE::JSON::from_json(it->value, value))
                return false;
            values.emplace(key, value);
        }
        return true;
    }
};
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
template <class TJson, typename T>
struct NodeReader
{
    static bool from_json(const TJson& json, T& value, const char* key)
    {
        if (key == nullptr)
            return false;

        // Try to find a member with the given key
        rapidjson::Value::ConstMemberIterator member = json.FindMember(key);
        if (member == json.MemberEnd())
            return false;

        return FBE::JSON::from_json(member->value, value);
    }
};

template <class TJson, typename T>
bool from_json(const TJson& json, T& value, const char* key)
{
    return NodeReader<TJson, T>::from_json(json, value, key);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    code = R"CODE(
template <class TJson, typename T>
struct ChildNodeReader
{
    static bool from_json_child(const TJson& json, T& value, const char* key)
    {
        if (key == nullptr)
            return false;

        // Try to find a member with the given key
        rapidjson::Value::ConstMemberIterator member = json.FindMember(key);
        if (member == json.MemberEnd())
            return false;

        // Schema validation
        if (member->value.IsNull() || !member->value.IsObject())
            return false;

        // Deserialize the child object
        return FBE::JSON::from_json(member->value.GetObj(), value);
    }
};

template <class TJson, typename T>
bool from_json_child(const TJson& json, T& value, const char* key)
{
    return ChildNodeReader<TJson, T>::from_json_child(json, value, key);
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace JSON");
}

void GeneratorCpp::GenerateFBE_Header(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the common file
    CppCommon::Path common = path / "fbe.h";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    // Generate imports
    GenerateImports();

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate common body
    GenerateBufferWrapper_Header();
    GenerateDecimalWrapper_Header();
    GenerateFlagsWrapper_Header();
    GenerateTimeWrapper_Header();
    GenerateUUIDWrapper_Header();
    GenerateFBEBuffer_Header();
    GenerateFBEBaseModel_Header();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate common footer
    GenerateFooter();

    // Store the common file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBE_Source(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the common file
    CppCommon::Path common = path / "fbe.cpp";
    WriteBegin();

    // Generate common source
    GenerateSource("FBE");

    // Generate imports
    GenerateImports("fbe.h");

    // Generate imports source
    GenerateImportsSource();

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate common body
    GenerateBufferWrapper_Source();
    GenerateTimeWrapper_Source();
    GenerateUUIDWrapper_Source();
    GenerateFBEBuffer_Source();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate common footer
    GenerateFooter();

    // Store the common file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEModels_Header(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the field models file
    CppCommon::Path common = path / "fbe_models.h";
    WriteBegin();

    // Generate field models header
    GenerateHeader("FBE");

    // Generate imports
    GenerateImports("fbe.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate field models
    GenerateFBEFieldModel_Header();
    GenerateFBEFieldModelDecimal_Header();
    GenerateFBEFieldModelUUID_Header();
    GenerateFBEFieldModelBytes_Header();
    GenerateFBEFieldModelString_Header();
    GenerateFBEFieldModelOptional_Header();
    GenerateFBEFieldModelArray_Header();
    GenerateFBEFieldModelVector_Header();
    GenerateFBEFieldModelMap_Header();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate inline import
    GenerateImports("fbe_models.inl");

    // Generate field models footer
    GenerateFooter();

    // Store the field models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEModels_Inline(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the field models file
    CppCommon::Path common = path / "fbe_models.inl";
    WriteBegin();

    // Generate field models inline
    GenerateInline("FBE");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate field models
    GenerateFBEFieldModel_Inline();
    GenerateFBEFieldModelOptional_Inline();
    GenerateFBEFieldModelArray_Inline();
    GenerateFBEFieldModelVector_Inline();
    GenerateFBEFieldModelMap_Inline();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate field models footer
    GenerateFooter();

    // Store the field models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEModels_Source(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the field models file
    CppCommon::Path common = path / "fbe_models.cpp";
    WriteBegin();

    // Generate field models source
    GenerateSource("FBE");

    // Generate imports
    GenerateImports("fbe_models.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate field models
    GenerateFBEFieldModelDecimal_Source();
    GenerateFBEFieldModelUUID_Source();
    GenerateFBEFieldModelBytes_Source();
    GenerateFBEFieldModelString_Source();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate field models footer
    GenerateFooter();

    // Store the field models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEFinalModels_Header(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the final models file
    CppCommon::Path common = path / "fbe_final_models.h";
    WriteBegin();

    // Generate final models header
    GenerateHeader("FBE");

    // Generate imports
    GenerateImports("fbe.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate final models
    GenerateFBEFinalModel_Header();
    GenerateFBEFinalModelDecimal_Header();
    GenerateFBEFinalModelUUID_Header();
    GenerateFBEFinalModelBytes_Header();
    GenerateFBEFinalModelString_Header();
    GenerateFBEFinalModelOptional_Header();
    GenerateFBEFinalModelArray_Header();
    GenerateFBEFinalModelVector_Header();
    GenerateFBEFinalModelMap_Header();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate inline import
    GenerateImports("fbe_final_models.inl");

    // Generate final models footer
    GenerateFooter();

    // Store the final models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEFinalModels_Inline(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the final models file
    CppCommon::Path common = path / "fbe_final_models.inl";
    WriteBegin();

    // Generate final models inline
    GenerateInline("FBE");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate final models
    GenerateFBEFinalModel_Inline();
    GenerateFBEFinalModelOptional_Inline();
    GenerateFBEFinalModelArray_Inline();
    GenerateFBEFinalModelVector_Inline();
    GenerateFBEFinalModelMap_Inline();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate final models footer
    GenerateFooter();

    // Store the final models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEFinalModels_Source(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the final models file
    CppCommon::Path common = path / "fbe_final_models.cpp";
    WriteBegin();

    // Generate final models source
    GenerateSource("FBE");

    // Generate imports
    GenerateImports("fbe_final_models.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate final models
    GenerateFBEFinalModelDecimal_Source();
    GenerateFBEFinalModelUUID_Source();
    GenerateFBEFinalModelBytes_Source();
    GenerateFBEFinalModelString_Source();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate final models footer
    GenerateFooter();

    // Store the final models file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEProtocol_Header(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the protocol file
    CppCommon::Path common = path / "fbe_protocol.h";
    WriteBegin();

    // Generate protocol header
    GenerateHeader("FBE");

    // Generate imports
    GenerateImports("fbe.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate protocol
    GenerateFBESender_Header();
    GenerateFBEReceiver_Header();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate protocol footer
    GenerateFooter();

    // Store the protocol file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEProtocol_Source(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the protocol file
    CppCommon::Path common = path / "fbe_protocol.cpp";
    WriteBegin();

    // Generate protocol source
    GenerateSource("FBE");

    // Generate imports
    GenerateImports("fbe_protocol.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate protocol
    GenerateFBESender_Source();
    GenerateFBEReceiver_Source();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate protocol footer
    GenerateFooter();

    // Store the protocol file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GenerateFBEJson_Header(const CppCommon::Path& path)
{
    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the JSON file
    CppCommon::Path common = path / "fbe_json.h";
    WriteBegin();

    // Generate JSON header
    GenerateHeader("FBE");

    // Generate imports
    GenerateImports("fbe.h");
    GenerateImportsJson();

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate JSON
    GenerateFBEJson();

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate JSON footer
    GenerateFooter();

    // Store the JSON file
    WriteEnd();
    Store(common);
}

void GeneratorCpp::GeneratePackage_Header(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + ".h";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports(p);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& e : p->body->enums)
            GenerateEnum(p, e);

        // Generate child flags
        for (const auto& f : p->body->flags)
            GenerateFlags(p, f);

        // Generate child structs
        for (const auto& s : p->body->structs)
            GenerateStruct_Header(p, s);
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackage_Source(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + ".cpp";
    WriteBegin();

    // Generate package source
    GenerateSource(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports(*p->name + ".h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& e : p->body->enums)
        {
            // Generate enum output stream
            GenerateEnumOutputStream(e);

            // Generate enum logging stream
            if (Logging())
                GenerateEnumLoggingStream(e);
        }

        // Generate child flags
        for (const auto& f : p->body->flags)
        {
            // Generate flags output stream
            GenerateFlagsOutputStream(f);

            // Generate flags logging stream
            if (Logging())
                GenerateFlagsLoggingStream(f);
        }

        // Generate child structs
        for (const auto& s : p->body->structs)
        {
            GenerateStruct_Source(p, s);

            // Generate struct output stream
            GenerateStructOutputStream(p, s);

            // Generate struct logging stream
            if (Logging())
                GenerateStructLoggingStream(p, s);
        }
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackage_Json(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + "_json.h";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImportsJson(p);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");
    WriteLine();
    WriteLineIndent("namespace JSON {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& e : p->body->enums)
            GenerateEnumJson(p, e);

        // Generate child flags
        for (const auto& f : p->body->flags)
            GenerateFlagsJson(p, f);

        // Generate child structs
        for (const auto& s : p->body->structs)
            GenerateStructJson(p, s);
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace JSON");
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageModels_Header(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + "_models.h";
    WriteBegin();

    // Generate package models header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports("fbe_models.h");
    GenerateImportsModels(p, false);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& e : p->body->enums)
        {
            // Generate enum field model
            GenerateEnumFieldModel(p, e);
        }

        // Generate child flags
        for (const auto& f : p->body->flags)
        {
            // Generate flags field model
            GenerateFlagsFieldModel(p, f);
        }

        // Generate child structs
        for (const auto& s : p->body->structs)
        {
            // Generate struct field models
            GenerateStructFieldModel_Header(p, s);
            GenerateStructModel_Header(p, s);
        }
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageModels_Source(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + "_models.cpp";
    WriteBegin();

    // Generate package models source
    GenerateSource(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports(*p->name + "_models.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child structs
        for (const auto& s : p->body->structs)
        {
            // Generate struct field models
            GenerateStructFieldModel_Source(p, s);
            GenerateStructModel_Source(p, s);
        }
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageFinalModels_Header(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + "_final_models.h";
    WriteBegin();

    // Generate package final models header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports("fbe_final_models.h");
    GenerateImportsModels(p, true);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child enums
        for (const auto& e : p->body->enums)
        {
            // Generate enum final model
            GenerateEnumFinalModel(p, e);
        }

        // Generate child flags
        for (const auto& f : p->body->flags)
        {
            // Generate flags final model
            GenerateFlagsFinalModel(p, f);
        }

        // Generate child structs
        for (const auto& s : p->body->structs)
        {
            // Generate struct final models
            GenerateStructFinalModel_Header(p, s);
            GenerateStructModelFinal_Header(p, s);
        }
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageFinalModels_Source(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + "_final_models.cpp";
    WriteBegin();

    // Generate package final models source
    GenerateSource(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports(*p->name + "_final_models.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");

    // Generate namespace body
    if (p->body)
    {
        // Generate child structs
        for (const auto& s : p->body->structs)
        {
            // Generate struct final models
            GenerateStructFinalModel_Source(p, s);
            GenerateStructModelFinal_Source(p, s);
        }
    }

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageProtocol_Header(const std::shared_ptr<Package>& p, bool final)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + (final ? "_final" : "") + "_protocol.h";
    WriteBegin();

    // Generate package protocol header
    GenerateHeader(CppCommon::Path(_input).filename().string());

    // Generate imports
    GenerateImports("fbe_protocol.h");
    GenerateImportsProtocol(p, final);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    // Generate protocol version
    GenerateProtocolVersion(p);

    // Generate sender & receiver
    GenerateSender_Header(p, final);
    GenerateReceiver_Header(p, final);
    if (!final)
        GenerateProxy_Header(p, final);
    GenerateClient_Header(p, final);

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GeneratePackageProtocol_Source(const std::shared_ptr<Package>& p, bool final)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate the output file
    output /= *p->name + (final ? "_final" : "") + "_protocol.cpp";
    WriteBegin();

    // Generate package protocol source
    GenerateSource(CppCommon::Path(_input).filename().string());

    // Generate warnings header
    GenerateWarningsHeader();

    // Generate imports
    GenerateImports(*p->name + (final ? "_final" : "") + "_protocol.h");

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace FBE {");
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    // Generate sender & receiver
    GenerateSender_Source(p, final);
    GenerateReceiver_Source(p, final);
    if (!final)
        GenerateProxy_Source(p, final);
    GenerateClient_Source(p, final);

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
    WriteLine();
    WriteLineIndent("} // namespace FBE");

    // Generate warnings footer
    GenerateWarningsFooter();

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorCpp::GenerateEnum(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    // Generate enum begin
    WriteLine();
    WriteIndent("enum class " + std::string(e->attributes->deprecated ? "[[deprecated]] " : "") + *e->name);
    if (e->base && !e->base->empty())
        Write(" : " + ConvertEnumType(*e->base));
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Generate enum body
    if (e->body)
    {
        for (const auto& value : e->body->values)
        {
            WriteIndent(*value->name);
            if (value->attributes->deprecated)
                Write(" [[deprecated]]");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(" = " + ConvertConstant(enum_type, *value->value->constant, false));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(" = " + ConvertConstant("", *value->value->reference, false));
            }
            WriteLine(",");
        }
    }

    // Generate enum end
    Indent(-1);
    WriteLineIndent("};");

    // Generate enum output stream operator declaration
    WriteLine();
    WriteLineIndent("std::ostream& operator<<(std::ostream& stream, " + *e->name + " value);");

    // Generate enum formatter declaration
    WriteLine();
    WriteLineIndent("#if defined(FMT_VERSION) && (FMT_VERSION >= 90000)");
    WriteLineIndent("} template <> struct fmt::formatter<" + *p->name + "::" + *e->name + "> : ostream_formatter {}; namespace " + *p->name + " {");
    WriteLineIndent("#endif");

    // Generate enum logging stream operator declaration
    WriteLine();
    WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
    WriteLineIndent("CppLogging::Record& operator<<(CppLogging::Record& record, " + *e->name + " value);");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateEnumOutputStream(const std::shared_ptr<EnumType>& e)
{
    // Generate enum output stream operator begin
    WriteLine();
    WriteLineIndent("std::ostream& operator<<(std::ostream& stream, " + *e->name + " value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate enum output stream operator body
    if (e->body && !e->body->values.empty())
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
            WriteLineIndent("if (value == " + *e->name + "::" + *(*it)->name + ")" + " return stream << \"" + *(*it)->name + "\";");
        WriteLineIndent("return stream << \"<unknown>\";");
    }
    else
        WriteLineIndent("return stream << \"<empty>\";");

    // Generate enum output stream operator end
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateEnumLoggingStream(const std::shared_ptr<EnumType>& e)
{
    // Generate enum logging stream operator begin
    WriteLine();
    WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
    WriteLineIndent("CppLogging::Record& operator<<(CppLogging::Record& record, " + *e->name + " value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate enum logging stream operator body
    if (e->body && !e->body->values.empty())
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
            WriteLineIndent("if (value == " + *e->name + "::" + *(*it)->name + ")" + " return record.StoreCustom(\"" + *(*it)->name + "\");");
        WriteLineIndent("return record.StoreCustom(\"<unknown>\");");
    }
    else
        WriteLineIndent("return record.StoreCustom(\"<empty>\");");

    // Generate enum logging stream operator end
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateEnumJson(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    std::string enum_name = "::" + *p->name + "::" + *e->name;
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    std::string json = R"CODE(
template <class TWriter>
struct ValueWriter<TWriter, _ENUM_NAME_>
{
    static bool to_json(TWriter& writer, const _ENUM_NAME_& value, bool scope = true)
    {
        return FBE::JSON::to_json(writer, (_ENUM_TYPE_)value);
    }
};

template <class TJson>
struct ValueReader<TJson, _ENUM_NAME_>
{
    static bool from_json(const TJson& json, _ENUM_NAME_& value)
    {
        _ENUM_TYPE_ raw;
        if (!FBE::JSON::from_json(json, raw))
            return false;

        value = (_ENUM_NAME_)raw;
        return true;
    }
};
)CODE";

    // Prepare enum template
    json = std::regex_replace(json, std::regex("_ENUM_NAME_"), enum_name);
    json = std::regex_replace(json, std::regex("_ENUM_TYPE_"), enum_base_type);
    json = std::regex_replace(json, std::regex("\n"), EndLine());

    // Generate enum JSON
    Write(json);
}

void GeneratorCpp::GenerateEnumFieldModel(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    std::string enum_name = "::" + *p->name + "::" + *e->name;
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    std::string code = R"CODE(
// Fast Binary Encoding _ENUM_NAME_ field model
template <>
class FieldModel<_ENUM_NAME_> : public FieldModelBase<_ENUM_NAME_, _ENUM_TYPE_>
{
public:
    using FieldModelBase<_ENUM_NAME_, _ENUM_TYPE_>::FieldModelBase;
};
)CODE";

    // Prepare enum template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), enum_name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), enum_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate enum field model
    Write(code);
}

void GeneratorCpp::GenerateEnumFinalModel(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    std::string enum_name = "::" + *p->name + "::" + *e->name;
    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);

    std::string code = R"CODE(
// Fast Binary Encoding _ENUM_NAME_ final model
template <>
class FinalModel<_ENUM_NAME_> : public FinalModelBase<_ENUM_NAME_, _ENUM_TYPE_>
{
public:
    using FinalModelBase<_ENUM_NAME_, _ENUM_TYPE_>::FinalModelBase;
};
)CODE";

    // Prepare enum template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), enum_name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), enum_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate enum final model
    Write(code);
}

void GeneratorCpp::GenerateFlags(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    // Generate flags begin
    WriteLine();
    WriteIndent("enum class " + std::string(f->attributes->deprecated ? "[[deprecated]] " : "") + *f->name);
    if (f->base && !f->base->empty())
        Write(" : " + ConvertEnumType(*f->base));
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Generate flags body
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteIndent(*value->name);
            if (value->attributes->deprecated)
                Write(" [[deprecated]]");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(" = " + ConvertConstant(flags_type, *value->value->constant, false));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(" = " + ConvertConstant("", *value->value->reference, false));
            }
            WriteLine(",");
        }
    }

    // Generate flags end
    Indent(-1);
    WriteLineIndent("};");
    WriteLine();
    WriteLineIndent("FBE_ENUM_FLAGS(" + *f->name + ")");

    // Generate flags output stream operator declaration
    WriteLine();
    WriteLineIndent("std::ostream& operator<<(std::ostream& stream, " + *f->name + " value);");

    // Generate flags formatter declaration
    WriteLine();
    WriteLineIndent("#if defined(FMT_VERSION) && (FMT_VERSION >= 90000)");
    WriteLineIndent("} template <> struct fmt::formatter<" + *p->name + "::" + *f->name + "> : ostream_formatter {}; namespace " + *p->name + " {");
    WriteLineIndent("#endif");

    // Generate flags logging stream operator declaration
    WriteLine();
    WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
    WriteLineIndent("CppLogging::Record& operator<<(CppLogging::Record& record, " + *f->name + " value);");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateFlagsOutputStream(const std::shared_ptr<FlagsType>& f)
{
    // Generate flags output stream operator begin
    WriteLine();
    WriteLineIndent("std::ostream& operator<<(std::ostream& stream, " + *f->name + " value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate flags output stream operator body
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("bool first = true;");
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            WriteLineIndent("if ((value & " + *f->name + "::" + *(*it)->name + ") && ((value & " + *f->name + "::" + *(*it)->name + ") == " + *f->name + "::" + *(*it)->name + "))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("stream << (first ? \"\" : \"|\") << \"" + *(*it)->name + "\";");
            WriteLineIndent("first = false;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return stream;");

    // Generate flags output stream operator end
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateFlagsLoggingStream(const std::shared_ptr<FlagsType>& f)
{
    // Generate flags logging stream operator begin
    WriteLine();
    WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
    WriteLineIndent("CppLogging::Record& operator<<(CppLogging::Record& record, " + *f->name + " value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate flags output stream operator body
    WriteLineIndent("const size_t begin = record.StoreListBegin();");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("bool first = true;");
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            WriteLineIndent("if ((value & " + *f->name + "::" + *(*it)->name + ") && ((value & " + *f->name + "::" + *(*it)->name + ") == " + *f->name + "::" + *(*it)->name + "))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("record.StoreList((first ? \"\" : \"|\")).StoreList(\"" + *(*it)->name + "\");");
            WriteLineIndent("first = false;");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return record.StoreListEnd(begin);");

    // Generate flags logging stream operator end
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateFlagsJson(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    std::string flags_name = "::" + *p->name + "::" + *f->name;
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    std::string json = R"CODE(
template <class TWriter>
struct ValueWriter<TWriter, _FLAGS_NAME_>
{
    static bool to_json(TWriter& writer, const _FLAGS_NAME_& value, bool scope = true)
    {
        return FBE::JSON::to_json(writer, (_FLAGS_TYPE_)value);
    }
};

template <class TJson>
struct ValueReader<TJson, _FLAGS_NAME_>
{
    static bool from_json(const TJson& json, _FLAGS_NAME_& value)
    {
        _FLAGS_TYPE_ raw;
        if (!FBE::JSON::from_json(json, raw))
            return false;

        value = (_FLAGS_NAME_)raw;
        return true;
    }
};
)CODE";

    // Prepare flags template
    json = std::regex_replace(json, std::regex("_FLAGS_NAME_"), flags_name);
    json = std::regex_replace(json, std::regex("_FLAGS_TYPE_"), flags_base_type);
    json = std::regex_replace(json, std::regex("\n"), EndLine());

    // Generate flags JSON
    Write(json);
}

void GeneratorCpp::GenerateFlagsFieldModel(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    std::string flags_name = "::" + *p->name + "::" + *f->name;
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    std::string code = R"CODE(
// Fast Binary Encoding _FLAGS_NAME_ field model
template <>
class FieldModel<_FLAGS_NAME_> : public FieldModelBase<_FLAGS_NAME_, _FLAGS_TYPE_>
{
public:
    using FieldModelBase<_FLAGS_NAME_, _FLAGS_TYPE_>::FieldModelBase;
};
)CODE";

    // Prepare flags template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), flags_name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), flags_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate flags field model
    Write(code);
}

void GeneratorCpp::GenerateFlagsFinalModel(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    std::string flags_name = "::" + *p->name + "::" + *f->name;
    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);

    std::string code = R"CODE(
// Fast Binary Encoding _FLAGS_NAME_ final model
template <>
class FinalModel<_FLAGS_NAME_> : public FinalModelBase<_FLAGS_NAME_, _FLAGS_TYPE_>
{
public:
    using FinalModelBase<_FLAGS_NAME_, _FLAGS_TYPE_>::FinalModelBase;
};
)CODE";

    // Prepare flags template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), flags_name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), flags_base_type);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    // Generate flags final model
    Write(code);
}

void GeneratorCpp::GenerateStruct_Header(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate struct response forward declaration
    if (s->response)
    {
        std::string response = *s->response->response;
        bool imported = CppCommon::StringUtils::ReplaceAll(response, ".", "");
        if (!imported)
        {
            WriteLine();
            WriteLineIndent("struct " + response + ";");
        }
    }

    // Generate struct begin
    WriteLine();
    WriteIndent("struct " + std::string(s->attributes->deprecated ? "[[deprecated]] " : "") + *s->name);
    if (s->base && !s->base->empty())
        Write(" : public " + ConvertTypeName(*p->name, *s->base, false));
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate struct response type definition
    if (s->response)
    {
        std::string response = *s->response->response;
        CppCommon::StringUtils::ReplaceAll(response, ".", "::");
        WriteLineIndent("typedef " + response + " Response;");
        if (s->body && !s->body->fields.empty())
            WriteLine();
    }

    // Generate struct body
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteIndent();
            if (field->attributes && field->attributes->deprecated)
                Write("[[deprecated]] ");
            WriteLine(ConvertTypeName(*p->name, *field) + " " + *field->name + ";");
        }
        if (!s->body->fields.empty())
            WriteLine();
    }

    // Generate struct FBE type property
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("size_t fbe_type() const noexcept { return " + ConvertTypeName(*p->name, *s->base, false) + "::fbe_type(); }");
    else
        WriteLineIndent("size_t fbe_type() const noexcept { return " + std::to_string(s->type) + "; }");

    // Generate struct default constructor
    bool first = true;
    WriteLine();
    WriteLineIndent(*s->name + "();");

    // Generate struct initialization constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        size_t args = 0;
        if (s->base && !s->base->empty())
            ++args;
        if (s->body && !s->body->fields.empty())
            args += s->body->fields.size();
        WriteIndent(((args <= 1) ? "explicit " : "") + *s->name + "(");
        if (s->base && !s->base->empty())
        {
            Write("const " + ConvertTypeName(*p->name, *s->base, false) + "& base");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "" : ", ") + ConvertTypeNameAsArgument(*p->name, *field) + " arg_" + *field->name);
                first = false;
            }
        }
        WriteLine(");");
    }

    // Generate struct copy/mode constructor, destructor and assign operators
    WriteLineIndent(*s->name + "(const " + *s->name + "& other) = default;");
    WriteLineIndent(*s->name + "(" + *s->name + "&& other) = default;");
    WriteLineIndent("~" + *s->name + "() = default;");
    WriteLine();
    WriteLineIndent(*s->name + "& operator=(const " + *s->name + "& other) = default;");
    WriteLineIndent(*s->name + "& operator=(" + *s->name + "&& other) = default;");

    // Generate struct compare operators
    WriteLine();
    WriteLineIndent("bool operator==(const " + *s->name + "& other) const noexcept;");
    WriteLineIndent("bool operator!=(const " + *s->name + "& other) const noexcept { return !operator==(other); }");
    WriteLineIndent("bool operator<(const " + *s->name + "& other) const noexcept;");
    WriteLineIndent("bool operator<=(const " + *s->name + "& other) const noexcept { return operator<(other) || operator==(other); }");
    WriteLineIndent("bool operator>(const " + *s->name + "& other) const noexcept { return !operator<=(other); }");
    WriteLineIndent("bool operator>=(const " + *s->name + "& other) const noexcept { return !operator<(other); }");

    // Generate struct string convert
    WriteLine();
    WriteLineIndent("std::string string() const { std::stringstream ss; ss << *this; return ss.str(); }");

    // Generate struct output stream operator
    WriteLine();
    WriteLineIndent("friend std::ostream& operator<<(std::ostream& stream, const " + *s->name + "& value);");

    // Generate struct output stream operator
    if (Logging())
    {
        WriteLine();
        WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
        WriteLineIndent("friend CppLogging::Record& operator<<(CppLogging::Record& record, const " + *s->name + "& value);");
        WriteLineIndent("#endif");
    }

    // Generate struct swap methods
    WriteLine();
    WriteLineIndent("void swap(" + *s->name + "& other) noexcept;");
    WriteLineIndent("friend void swap(" + *s->name + "& value1, " + *s->name + "& value2) noexcept { value1.swap(value2); }");

    // Generate struct end
    Indent(-1);
    WriteLineIndent("};");

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);

    // Generate struct formatter
    GenerateStructFormatter(p, s);

    // Generate struct hash
    GenerateStructHash(p, s);

    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");
}

void GeneratorCpp::GenerateStruct_Source(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    WriteLine();

    // Generate struct default constructor
    bool first = true;
    WriteLineIndent(*s->name + "::" + *s->name + "()");
    Indent(1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent(": " + ConvertTypeName(*p->name, *s->base, false) + "()");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent(std::string(first ? ": " : ", ") + *field->name + "(" + ((field->value || IsPrimitiveType(*field->type, field->optional)) ? ConvertDefault(*p->name, *field) : "") + ")");
            first = false;
        }
    }
    Indent(-1);
    WriteLineIndent("{}");

    // Generate struct initialization constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        WriteLine();
        WriteIndent(*s->name + "::" + *s->name + "(");
        if (s->base && !s->base->empty())
        {
            Write("const " + ConvertTypeName(*p->name, *s->base, false) + "& base");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "" : ", ") + ConvertTypeNameAsArgument(*p->name, *field) + " arg_" + *field->name);
                first = false;
            }
        }
        WriteLine(")");
        Indent(1);
        first = true;
        if (s->base && !s->base->empty())
        {
            WriteLineIndent(": " + ConvertTypeName(*p->name, *s->base, false) + "(base)");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLineIndent(std::string(first ? ": " : ", ") + *field->name + "(arg_" + *field->name + ")");
                first = false;
            }
        }
        Indent(-1);
        WriteLineIndent("{}");
    }

    // Generate struct compare operators
    WriteLine();
    WriteLineIndent("bool " + *s->name + "::operator==(const " + *s->name + "& other) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return (");
    Indent(1);
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent(ConvertTypeName(*p->name, *s->base, false) + "::operator==(other)");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent(std::string(first ? "(" : "&& (") + *field->name + " == other." + *field->name + ")");
                first = false;
            }
        }
    }
    if (!s->keys)
    {
        WriteLineIndent(std::string(first ? "true" : "&& true"));
        first = false;
    }
    WriteLineIndent(");");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("}");

    WriteLine();
    WriteLineIndent("bool " + *s->name + "::operator<(const " + *s->name + "& other) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (" + ConvertTypeName(*p->name, *s->base, false) + "::operator<(other))");
        Indent(1);
        WriteLineIndent("return true;");
        Indent(-1);
        WriteLineIndent("if (other." + ConvertTypeName(*p->name, *s->base, false) + "::operator<(*this))");
        Indent(1);
        WriteLineIndent("return false;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("if (" + *field->name + " < other." + *field->name + ")");
                Indent(1);
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("if (other." + *field->name + " < " + *field->name + ")");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct swap method
    WriteLine();
    WriteLineIndent("void " + *s->name + "::swap(" + *s->name + "& other) noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("using std::swap;");
    if (s->base && !s->base->empty())
        WriteLineIndent(ConvertTypeName(*p->name, *s->base, false) + "::swap(other);");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("swap(" + *field->name + ", other." + *field->name + ");");
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateStructOutputStream(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate struct output stream operator begin
    WriteLine();
    WriteLineIndent("std::ostream& operator<<(std::ostream& stream, const " + *s->name + "& value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct output stream operator body
    WriteLineIndent("stream << \"" + *s->name + "(\";");
    bool first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("stream << (const " + ConvertTypeName(*p->name, *s->base, false) + "&)value;");
        first = false;
    }
    if (s->body)
    {
        // Generate fields output stream operator calls
        for (const auto& field : s->body->fields)
        {
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=***\";");
            else if (field->array)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[" + std::to_string(field->N) + "][\"" + ";");
                WriteLineIndent("for (size_t i = 0; i < " + std::to_string(field->N) + "; ++i)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "value." + *field->name + "[i]", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \"]\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->vector)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[\" << " + "value." + *field->name + ".size()" + " << \"][\"" + ";");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \"]\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->list)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[\" << " + "value." + *field->name + ".size()" + "<< \"]<\"" + ";");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \">\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[\" << " + "value." + *field->name + ".size()" + "<< \"]{\"" + ";");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \"}\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[\" << " + "value." + *field->name + ".size()" + "<< \"]<{\"" + ";");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "it.first", false, true));
                WriteLineIndent("stream << \"->\";");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "it.second", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \"}>\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->hash)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=[\" << " + "value." + *field->name + ".size()" + "<< \"][{\"" + ";");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "it.first", false, true));
                WriteLineIndent("stream << \"->\";");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "it.second", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("stream << \"}]\";");
                Indent(-1);
                WriteLineIndent("}");
            }
            else
                WriteLineIndent("stream << \"" + std::string(first ? "" : ",") + *field->name + "=\"; " + ConvertOutputStreamValue(*field->type, "value." + *field->name, field->optional, false));
            first = false;
        }
    }
    WriteLineIndent("stream << \")\";");
    WriteLineIndent("return stream;");

    // Generate struct output stream operator end
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateStructLoggingStream(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate struct logging stream operator begin
    WriteLine();
    WriteLineIndent("#if defined(LOGGING_PROTOCOL)");
    WriteLineIndent("CppLogging::Record& operator<<(CppLogging::Record& record, const " + *s->name + "& value)");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct output stream operator body
    WriteLineIndent("const size_t begin = record.StoreListBegin();");
    WriteLineIndent("record.StoreList(\"" + *s->name + "(\");");
    bool first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("record.StoreList((const " + ConvertTypeName(*p->name, *s->base, false) + "&)value);");
        first = false;
    }
    if (s->body)
    {
        // Generate fields output stream operator calls
        for (const auto& field : s->body->fields)
        {
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=***\");");
            else if (field->array)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[" + std::to_string(field->N) + "][\");");
                WriteLineIndent("for (size_t i = 0; i < " + std::to_string(field->N) + "; ++i)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "value." + *field->name + "[i]", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->vector)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[\").StoreList(value." + *field->name + ".size()).StoreList(\"][\");");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\"]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->list)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[\").StoreList(value." + *field->name + ".size()).StoreList(\"]<\");");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\">\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[\").StoreList(value." + *field->name + ".size()).StoreList(\"]{\");");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "it", field->optional, true));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\"}\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[\").StoreList(value." + *field->name + ".size()).StoreList(\"]<{\");");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->key, "it.first", false, true));
                WriteLineIndent("record.StoreList(\"->\");");
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "it.second", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\"}>\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->hash)
            {
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("bool first = true;");
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=[\").StoreList(value." + *field->name + ".size()).StoreList(\"][{\");");
                WriteLineIndent("for (const auto& it : value." + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertLoggingStreamValue(*field->key, "it.first", false, true));
                WriteLineIndent("record.StoreList(\"->\");");
                WriteLineIndent(ConvertLoggingStreamValue(*field->type, "it.second", field->optional, false));
                WriteLineIndent("first = false;");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("record.StoreList(\"}]\");");
                Indent(-1);
                WriteLineIndent("}");
            }
            else
                WriteLineIndent("record.StoreList(\"" + std::string(first ? "" : ",") + *field->name + "=\"); " + ConvertLoggingStreamValue(*field->type, "value." + *field->name, field->optional, false));
            first = false;
        }
    }
    WriteLineIndent("record.StoreList(\")\");");
    WriteLineIndent("return record.StoreListEnd(begin);");

    // Generate struct logging stream operator end
    Indent(-1);
    WriteLineIndent("}");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateStructFormatter(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate struct formatter
    WriteLine();
    WriteLineIndent("#if defined(FMT_VERSION) && (FMT_VERSION >= 90000)");
    WriteLineIndent("template <> struct fmt::formatter<" + *p->name + "::" + *s->name + "> : ostream_formatter {};");
    WriteLineIndent("#endif");
}

void GeneratorCpp::GenerateStructHash(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate struct hash
    WriteLine();
    WriteLineIndent("template<>");
    WriteLineIndent("struct std::hash<" + *p->name + "::" + *s->name + ">");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("typedef " + *p->name + "::" + *s->name + " argument_type;");
    WriteLineIndent("typedef size_t result_type;");
    WriteLine();
    WriteLineIndent("result_type operator() (const argument_type& value) const");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("result_type result = 17;");
    if (s->base && !s->base->empty())
        WriteLineIndent("result = result * 31 + std::hash<" + ConvertTypeName(*p->name, *s->base, false) + ">()(value);");
    if (s->body)
        for (const auto& field : s->body->fields)
            if (field->keys)
                WriteLineIndent("result = result * 31 + std::hash<decltype(value." + *field->name + ")>()(value." + *field->name + ");");
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateStructJson(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string struct_name = "::" + *p->name + "::" + *s->name;

    // Generate to_json() function
    WriteLine();
    WriteLineIndent("template <class TWriter>");
    WriteLineIndent("struct ValueWriter<TWriter, " + struct_name + ">");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("static bool to_json(TWriter& writer, const " + struct_name + "& value, bool scope = true)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (scope)");
    Indent(1);
    WriteLineIndent("if (!writer.StartObject())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    Indent(-1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (!FBE::JSON::to_json(writer, (const " + ConvertTypeName(*p->name, *s->base, false) + "&)value, false))");
        Indent(1);
        WriteLineIndent("return false;");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("if (!FBE::JSON::to_json_key(writer, \"" + *field->name + "\") || !FBE::JSON::to_json(writer, value." + *field->name + ", true))");
            Indent(1);
            WriteLineIndent("return false;");
            Indent(-1);
        }
    }
    WriteLineIndent("if (scope)");
    Indent(1);
    WriteLineIndent("if (!writer.EndObject())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("};");

    // Generate from_json() function
    WriteLine();
    WriteLineIndent("template <class TJson>");
    WriteLineIndent("struct ValueReader<TJson, " + struct_name + ">");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("static bool from_json(const TJson& json, " + struct_name + "& value, const char* key = nullptr)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (key != nullptr)");
    Indent(1);
    WriteLineIndent("return FBE::JSON::from_json_child(json, value, key);");
    Indent(-1);
    WriteLineIndent("bool result = true;");
    if (s->base && !s->base->empty())
        WriteLineIndent("result &= FBE::JSON::from_json(json, (" + ConvertTypeName(*p->name, *s->base, false) + "&)value);");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("result &= FBE::JSON::from_json(json, value." + *field->name + ", \"" + *field->name + "\");");
    WriteLineIndent("return result;");
    Indent(-1);
    WriteLineIndent("}");
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateStructFieldModel_Header(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string struct_name = "::" + *p->name + "::" + *s->name;

    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + struct_name + " field model");
    WriteLineIndent("template <>");
    WriteLineIndent("class FieldModel<" + struct_name + ">");
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate struct field model constructor
    WriteLineIndent("FieldModel(FBEBuffer& buffer, size_t offset) noexcept;");

    // Generate struct field model FBE methods
    WriteLine();
    WriteLineIndent("// Get the field offset");
    WriteLineIndent("size_t fbe_offset() const noexcept { return _offset; }");
    WriteLineIndent("// Get the field size");
    WriteLineIndent("size_t fbe_size() const noexcept { return 4; }");
    WriteLineIndent("// Get the field body size");
    WriteLineIndent("size_t fbe_body() const noexcept;");
    WriteLineIndent("// Get the field extra size");
    WriteLineIndent("size_t fbe_extra() const noexcept;");
    WriteLineIndent("// Get the field type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("static constexpr size_t fbe_type() noexcept { return FieldModel<" + ConvertTypeName(*p->name, *s->base, false) + ">::fbe_type(); }");
    else
        WriteLineIndent("static constexpr size_t fbe_type() noexcept { return " + std::to_string(s->type) + "; }");
    WriteLine();
    WriteLineIndent("// Shift the current field offset");
    WriteLineIndent("void fbe_shift(size_t size) noexcept { _offset += size; }");
    WriteLineIndent("// Unshift the current field offset");
    WriteLineIndent("void fbe_unshift(size_t size) noexcept { _offset -= size; }");

    // Generate struct field model verify(), verify_fields() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("bool verify(bool fbe_verify_type = true) const noexcept;");
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("bool verify_fields(size_t fbe_struct_size) const noexcept;");

    // Generate struct field model get_begin(), get_end() methods
    WriteLine();
    WriteLineIndent("// Get the struct value (begin phase)");
    WriteLineIndent("size_t get_begin() const noexcept;");
    WriteLineIndent("// Get the struct value (end phase)");
    WriteLineIndent("void get_end(size_t fbe_begin) const noexcept;");

    // Generate struct field model get(), get_fields() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("void get(" + struct_name + "& fbe_value) const noexcept;");
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("void get_fields(" + struct_name + "& fbe_value, size_t fbe_struct_size) const noexcept;");

    // Generate struct field model set_begin(), set_end() methods
    WriteLine();
    WriteLineIndent("// Set the struct value (begin phase)");
    WriteLineIndent("size_t set_begin();");
    WriteLineIndent("// Set the struct value (end phase)");
    WriteLineIndent("void set_end(size_t fbe_begin);");

    // Generate struct field model set(), set_fields() methods
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("void set(const " + struct_name + "& fbe_value) noexcept;");
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("void set_fields(const " + struct_name + "& fbe_value) noexcept;");

    // Generate struct field model buffer & offset
    Indent(-1);
    WriteLine();
    WriteLineIndent("private:");
    Indent(1);
    WriteLineIndent("FBEBuffer& _buffer;");
    WriteLineIndent("size_t _offset;");

    // Generate struct field model accessors
    Indent(-1);
    WriteLine();
    WriteLineIndent("public:");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("FieldModel<" + ConvertTypeName(*p->name, *s->base, false) + "> parent;");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->array)
                WriteLineIndent("FieldModelArray<" + ConvertTypeName(*p->name, *field->type, field->optional) + ", " + std::to_string(field->N) + "> " + *field->name + ";");
            else if (field->vector || field->list || field->set)
                WriteLineIndent("FieldModelVector<" + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
            else if (field->map || field->hash)
                WriteLineIndent("FieldModelMap<" + ConvertTypeName(*p->name, *field->key, false) + ", " + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
            else
                WriteLineIndent("FieldModel<" + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
        }
    }

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateStructFieldModel_Source(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string struct_name = "::" + *p->name + "::" + *s->name;
    std::string model_name = "FieldModel<" + struct_name + ">";

    // Generate struct field model constructor
    WriteLine();
    WriteLineIndent(model_name + "::FieldModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset)");
    Indent(1);
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent(", parent(buffer, " + prev_offset + " + " + prev_size + ")");
        prev_offset = "parent.fbe_offset()";
        prev_size = "parent.fbe_body() - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent(", " + *field->name + "(buffer, " + prev_offset + " + " + prev_size + ")");
            prev_offset = *field->name + ".fbe_offset()";
            prev_size = *field->name + ".fbe_size()";
        }
    }
    Indent(-1);
    WriteLineIndent("{}");
    WriteLine();

    // Generate struct field model FBE methods
    WriteLineIndent("size_t " + model_name + "::fbe_body() const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_result = 4 + 4");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_body() - 4 - 4");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_size()");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("size_t " + model_name + "::fbe_extra() const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));");
    WriteLineIndent("if ((fbe_struct_offset == 0) || ((_buffer.offset() + fbe_struct_offset + 4) > _buffer.size()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbe_struct_offset);");
    WriteLine();
    WriteLineIndent("size_t fbe_result = fbe_body()");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_extra()");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_extra()");
    WriteLineIndent(";");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.unshift(fbe_struct_offset);");
    WriteLine();
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model verify() method
    WriteLineIndent("bool " + model_name + "::verify(bool fbe_verify_type) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())");
    Indent(1);
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));");
    WriteLineIndent("if ((fbe_struct_offset == 0) || ((_buffer.offset() + fbe_struct_offset + 4 + 4) > _buffer.size()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_struct_offset));");
    WriteLineIndent("if (fbe_struct_size < (4 + 4))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_type = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_struct_offset + 4));");
    WriteLineIndent("if (fbe_verify_type && (fbe_struct_type != fbe_type()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbe_struct_offset);");
    WriteLineIndent("bool fbe_result = verify_fields(fbe_struct_size);");
    WriteLineIndent("_buffer.unshift(fbe_struct_offset);");
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model verify_fields() method
    WriteLineIndent("bool " + model_name + "::verify_fields(size_t fbe_struct_size) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("size_t fbe_current_size = 4 + 4;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbe_current_size + parent.fbe_body() - 4 - 4) > fbe_struct_size)");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
            WriteLineIndent("if (!parent.verify_fields(fbe_struct_size))");
            Indent(1);
            WriteLineIndent("return false;");
            Indent(-1);
            WriteLineIndent("fbe_current_size += parent.fbe_body() - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbe_current_size + " + *field->name + ".fbe_size()) > fbe_struct_size)");
                Indent(1);
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("if (!" + *field->name + ".verify())");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("fbe_current_size += " + *field->name + ".fbe_size();");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return true;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model get_begin() method
    WriteLineIndent("size_t " + model_name + "::get_begin() const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_offset = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset()));");
    WriteLineIndent("assert(((fbe_struct_offset > 0) && ((_buffer.offset() + fbe_struct_offset + 4 + 4) <= _buffer.size())) && \"Model is broken!\");");
    WriteLineIndent("if ((fbe_struct_offset == 0) || ((_buffer.offset() + fbe_struct_offset + 4 + 4) > _buffer.size()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_size = *((const uint32_t*)(_buffer.data() + _buffer.offset() + fbe_struct_offset));");
    WriteLineIndent("assert((fbe_struct_size >= (4 + 4)) && \"Model is broken!\");");
    WriteLineIndent("if (fbe_struct_size < (4 + 4))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbe_struct_offset);");
    WriteLineIndent("return fbe_struct_offset;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model get_end() method
    WriteLineIndent("void " + model_name + "::get_end(size_t fbe_begin) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbe_begin);");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model get() method
    WriteLineIndent("void " + model_name + "::get(" + struct_name + "& fbe_value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_begin = get_begin();");
    WriteLineIndent("if (fbe_begin == 0)");
    Indent(1);
    WriteLineIndent("return;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_size = *((const uint32_t*)(_buffer.data() + _buffer.offset()));");
    WriteLineIndent("get_fields(fbe_value, fbe_struct_size);");
    WriteLineIndent("get_end(fbe_begin);");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model get_fields() method
    WriteLineIndent("void " + model_name + "::get_fields(" + struct_name + "& fbe_value, size_t fbe_struct_size) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("size_t fbe_current_size = 4 + 4;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbe_current_size + parent.fbe_body() - 4 - 4) <= fbe_struct_size)");
            Indent(1);
            WriteLineIndent("parent.get_fields(fbe_value, fbe_struct_size);");
            Indent(-1);
            WriteLineIndent("fbe_current_size += parent.fbe_body() - 4 - 4;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbe_current_size + " + *field->name + ".fbe_size()) <= fbe_struct_size)");
                Indent(1);
                WriteLineIndent(*field->name + ".get(fbe_value." + *field->name + (field->value ? (", " + ConvertConstant(*field->type, *field->value, field->optional)) : "") +");");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                if (field->array)
                {
                    // Do nothing here...
                }
                else if (field->vector || field->list || field->set || field->map || field->hash || ((*field->type == "bytes") && !field->optional))
                    WriteLineIndent("fbe_value." + *field->name + ".clear();");
                else
                    WriteLineIndent("fbe_value." + *field->name + " = " + ConvertDefault(*p->name, *field) + ";");
                Indent(-1);
                WriteLineIndent("fbe_current_size += " + *field->name + ".fbe_size();");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model set_begin() method
    WriteLineIndent("size_t " + model_name + "::set_begin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("assert(((_buffer.offset() + fbe_offset() + fbe_size()) <= _buffer.size()) && \"Model is broken!\");");
    WriteLineIndent("if ((_buffer.offset() + fbe_offset() + fbe_size()) > _buffer.size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_size = (uint32_t)fbe_body();");
    WriteLineIndent("uint32_t fbe_struct_offset = (uint32_t)(_buffer.allocate(fbe_struct_size) - _buffer.offset());");
    WriteLineIndent("assert(((fbe_struct_offset > 0) && ((_buffer.offset() + fbe_struct_offset + fbe_struct_size) <= _buffer.size())) && \"Model is broken!\");");
    WriteLineIndent("if ((fbe_struct_offset == 0) || ((_buffer.offset() + fbe_struct_offset + fbe_struct_size) > _buffer.size()))");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("*((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_offset())) = fbe_struct_offset;");
    WriteLineIndent("*((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_struct_offset)) = fbe_struct_size;");
    WriteLineIndent("*((uint32_t*)(_buffer.data() + _buffer.offset() + fbe_struct_offset + 4)) = (uint32_t)fbe_type();");
    WriteLine();
    WriteLineIndent("_buffer.shift(fbe_struct_offset);");
    WriteLineIndent("return fbe_struct_offset;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model set_end() method
    WriteLineIndent("void " + model_name + "::set_end(size_t fbe_begin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbe_begin);");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model set() method
    WriteLineIndent("void " + model_name + "::set(const " + struct_name + "& fbe_value) noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_begin = set_begin();");
    WriteLineIndent("if (fbe_begin == 0)");
    Indent(1);
    WriteLineIndent("return;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("set_fields(fbe_value);");
    WriteLineIndent("set_end(fbe_begin);");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct field model set_fields() method
    WriteLineIndent("void " + model_name + "::set_fields(const " + struct_name + "& fbe_value) noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("parent.set_fields(fbe_value);");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(*field->name + ".set(fbe_value." + *field->name + ");");
    }
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateStructModel_Header(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    std::string struct_name = "::" + *p->name + "::" + *s->name;

    // Generate struct model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent("class " + *s->name + "Model : public FBE::Model");
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate struct model constructor
    WriteLineIndent(*s->name + "Model() : model(this->buffer(), 4) {}");
    WriteLineIndent(*s->name + "Model(const std::shared_ptr<FBEBuffer>& buffer) : FBE::Model(buffer), model(this->buffer(), 4) {}");

    // Generate struct model FBE methods
    WriteLine();
    WriteLineIndent("// Get the model size");
    WriteLineIndent("size_t fbe_size() const noexcept { return model.fbe_size() + model.fbe_extra(); }");
    WriteLineIndent("// Get the model type");
    WriteLineIndent("static constexpr size_t fbe_type() noexcept { return FieldModel<" + struct_name + ">::fbe_type(); }");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("bool verify();");

    // Generate struct model create_begin(), create_end() methods
    WriteLine();
    WriteLineIndent("// Create a new model (begin phase)");
    WriteLineIndent("size_t create_begin();");
    WriteLineIndent("// Create a new model (end phase)");
    WriteLineIndent("size_t create_end(size_t fbe_begin);");

    // Generate struct model serialize(), deserialize() methods
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("size_t serialize(const " + struct_name + "& value);");
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("size_t deserialize(" + struct_name + "& value) const noexcept;");

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("void next(size_t prev) noexcept { model.fbe_shift(prev); }");

    // Generate struct model accessor
    Indent(-1);
    WriteLine();
    WriteLineIndent("public:");
    Indent(1);
    WriteLineIndent("FieldModel<" + struct_name + "> model;");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("};");

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
}

void GeneratorCpp::GenerateStructModel_Source(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    std::string struct_name = "::" + *p->name + "::" + *s->name;
    std::string model_name = *s->name + "Model";

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("bool " + model_name + "::verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((this->buffer().offset() + model.fbe_offset() - 4) > this->buffer().size())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_full_size = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + model.fbe_offset() - 4));");
    WriteLineIndent("if (fbe_full_size < model.fbe_size())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return model.verify();");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model create_begin() method
    WriteLineIndent("size_t " + model_name + "::create_begin()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_begin = this->buffer().allocate(4 + model.fbe_size());");
    WriteLineIndent("return fbe_begin;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model create_end() method
    WriteLineIndent("size_t " + model_name + "::create_end(size_t fbe_begin)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_end = this->buffer().size();");
    WriteLineIndent("uint32_t fbe_full_size = (uint32_t)(fbe_end - fbe_begin);");
    WriteLineIndent("*((uint32_t*)(this->buffer().data() + this->buffer().offset() + model.fbe_offset() - 4)) = fbe_full_size;");
    WriteLineIndent("return fbe_full_size;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model serialize() method
    WriteLineIndent("size_t " + model_name + "::serialize(const " + struct_name + "& value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_begin = create_begin();");
    WriteLineIndent("model.set(value);");
    WriteLineIndent("size_t fbe_full_size = create_end(fbe_begin);");
    WriteLineIndent("return fbe_full_size;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model deserialize() method
    WriteLineIndent("size_t " + model_name + "::deserialize(" + struct_name + "& value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((this->buffer().offset() + model.fbe_offset() - 4) > this->buffer().size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("uint32_t fbe_full_size = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + model.fbe_offset() - 4));");
    WriteLineIndent("assert((fbe_full_size >= model.fbe_size()) && \"Model is broken!\");");
    WriteLineIndent("if (fbe_full_size < model.fbe_size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("model.get(value);");
    WriteLineIndent("return fbe_full_size;");
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
}

void GeneratorCpp::GenerateStructFinalModel_Header(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string struct_name = "::" + *p->name + "::" + *s->name;

    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + struct_name + " final model");
    WriteLineIndent("template <>");
    WriteLineIndent("class FinalModel<" + struct_name + ">");
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate struct final model constructor
    WriteLineIndent("FinalModel(FBEBuffer& buffer, size_t offset) noexcept;");

    // Generate struct final model FBE methods
    WriteLine();
    WriteLineIndent("// Get the allocation size");
    WriteLineIndent("size_t fbe_allocation_size(const " + struct_name + "& fbe_value) const noexcept;");
    WriteLineIndent("// Get the final offset");
    WriteLineIndent("size_t fbe_offset() const noexcept { return _offset; }");
    WriteLineIndent("// Set the final offset");
    WriteLineIndent("size_t fbe_offset(size_t offset) const noexcept { return _offset = offset; }");
    WriteLineIndent("// Get the final type");
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("static constexpr size_t fbe_type() noexcept { return FinalModel<" + ConvertTypeName(*p->name, *s->base, false) + ">::fbe_type(); }");
    else
        WriteLineIndent("static constexpr size_t fbe_type() noexcept { return " + std::to_string(s->type) + "; }");
    WriteLine();
    WriteLineIndent("// Shift the current final offset");
    WriteLineIndent("void fbe_shift(size_t size) noexcept { _offset += size; }");
    WriteLineIndent("// Unshift the current final offset");
    WriteLineIndent("void fbe_unshift(size_t size) noexcept { _offset -= size; }");

    // Generate struct final model verify(), verify_fields() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("size_t verify() const noexcept;");
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("size_t verify_fields() const noexcept;");

    // Generate struct final model get(), get_fields() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("size_t get(" + struct_name + "& fbe_value) const noexcept;");
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("size_t get_fields(" + struct_name + "& fbe_value) const noexcept;");

    // Generate struct final model set(), set_fields() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("size_t set(const " + struct_name + "& fbe_value) noexcept;");
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("size_t set_fields(const " + struct_name + "& fbe_value) noexcept;");

    // Generate struct final model buffer & offset
    Indent(-1);
    WriteLine();
    WriteLineIndent("private:");
    Indent(1);
    WriteLineIndent("FBEBuffer& _buffer;");
    WriteLineIndent("mutable size_t _offset;");

    // Generate struct final model accessors
    Indent(-1);
    WriteLine();
    WriteLineIndent("public:");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("FinalModel<" + ConvertTypeName(*p->name, *s->base, false) + "> parent;");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->array)
                WriteLineIndent("FinalModelArray<" + ConvertTypeName(*p->name, *field->type, field->optional) + ", " + std::to_string(field->N) + "> " + *field->name + ";");
            else if (field->vector || field->list || field->set)
                WriteLineIndent("FinalModelVector<" + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
            else if (field->map || field->hash)
                WriteLineIndent("FinalModelMap<" + ConvertTypeName(*p->name, *field->key, false) + ", " + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
            else
                WriteLineIndent("FinalModel<" + ConvertTypeName(*p->name, *field->type, field->optional) + "> " + *field->name + ";");
        }
    }

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateStructFinalModel_Source(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string struct_name = "::" + *p->name + "::" + *s->name;
    std::string model_name = "FinalModel<" + struct_name + ">";

    // Generate struct final model constructor
    WriteLine();
    WriteLineIndent(model_name + "::FinalModel(FBEBuffer& buffer, size_t offset) noexcept : _buffer(buffer), _offset(offset)");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent(", parent(buffer, 0)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent(", " + *field->name + "(buffer, 0)");
    Indent(-1);
    WriteLineIndent("{}");
    WriteLine();

    // Generate struct final model FBE methods
    WriteLineIndent("size_t " + model_name + "::fbe_allocation_size(const " + struct_name + "& fbe_value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_result = 0");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbe_allocation_size(fbe_value)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbe_allocation_size(fbe_value." + *field->name + ")");
    WriteLineIndent(";");
    Indent(-1);
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model verify() method
    WriteLineIndent("size_t " + model_name + "::verify() const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbe_offset());");
    WriteLineIndent("size_t fbe_result = verify_fields();");
    WriteLineIndent("_buffer.unshift(fbe_offset());");
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model verify_fields() method
    WriteLineIndent("size_t " + model_name + "::verify_fields() const noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("size_t fbe_current_offset = 0;");
        WriteLineIndent("size_t fbe_field_size;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset(fbe_current_offset);");
            WriteLineIndent("fbe_field_size = parent.verify_fields();");
            WriteLineIndent("if (fbe_field_size == std::numeric_limits<std::size_t>::max())");
            Indent(1);
            WriteLineIndent("return std::numeric_limits<std::size_t>::max();");
            Indent(-1);
            WriteLineIndent("fbe_current_offset += fbe_field_size;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset(fbe_current_offset);");
                WriteLineIndent("fbe_field_size = " + *field->name + ".verify();");
                WriteLineIndent("if (fbe_field_size == std::numeric_limits<std::size_t>::max())");
                Indent(1);
                WriteLineIndent("return std::numeric_limits<std::size_t>::max();");
                Indent(-1);
                WriteLineIndent("fbe_current_offset += fbe_field_size;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_offset;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model get() method
    WriteLineIndent("size_t " + model_name + "::get(" + struct_name + "& fbe_value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbe_offset());");
    WriteLineIndent("size_t fbe_result = get_fields(fbe_value);");
    WriteLineIndent("_buffer.unshift(fbe_offset());");
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model get_fields() method
    WriteLineIndent("size_t " + model_name + "::get_fields(" + struct_name + "& fbe_value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("size_t fbe_current_offset = 0;");
        WriteLineIndent("size_t fbe_current_size = 0;");
        WriteLineIndent("size_t fbe_field_size;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset(fbe_current_offset);");
            WriteLineIndent("fbe_field_size = parent.get_fields(fbe_value);");
            WriteLineIndent("fbe_current_offset += fbe_field_size;");
            WriteLineIndent("fbe_current_size += fbe_field_size;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset(fbe_current_offset);");
                WriteLineIndent("fbe_field_size = " + *field->name + ".get(fbe_value." + *field->name + ");");
                WriteLineIndent("fbe_current_offset += fbe_field_size;");
                WriteLineIndent("fbe_current_size += fbe_field_size;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_size;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model set() method
    WriteLineIndent("size_t " + model_name + "::set(const " + struct_name + "& fbe_value) noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbe_offset());");
    WriteLineIndent("size_t fbe_result = set_fields(fbe_value);");
    WriteLineIndent("_buffer.unshift(fbe_offset());");
    WriteLineIndent("return fbe_result;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct final model set_fields() method
    WriteLineIndent("size_t " + model_name + "::set_fields(const " + struct_name + "& fbe_value) noexcept");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("size_t fbe_current_offset = 0;");
        WriteLineIndent("size_t fbe_current_size = 0;");
        WriteLineIndent("size_t fbe_field_size;");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbe_offset(fbe_current_offset);");
            WriteLineIndent("fbe_field_size = parent.set_fields(fbe_value);");
            WriteLineIndent("fbe_current_offset += fbe_field_size;");
            WriteLineIndent("fbe_current_size += fbe_field_size;");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbe_offset(fbe_current_offset);");
                WriteLineIndent("fbe_field_size = " + *field->name + ".set(fbe_value." + *field->name + ");");
                WriteLineIndent("fbe_current_offset += fbe_field_size;");
                WriteLineIndent("fbe_current_size += fbe_field_size;");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_size;");
    }
    else
        WriteLineIndent("return 0;");
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateStructModelFinal_Header(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    std::string struct_name = "::" + *p->name + "::" + *s->name;

    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("class " + *s->name + "FinalModel : public FBE::Model");
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate struct model final constructor
    WriteLineIndent(*s->name + "FinalModel() : _model(this->buffer(), 8) {}");
    WriteLineIndent(*s->name + "FinalModel(const std::shared_ptr<FBEBuffer>& buffer) : FBE::Model(buffer), _model(this->buffer(), 8) {}");

    // Generate struct model final FBE methods
    WriteLine();
    WriteLineIndent("// Get the model type");
    WriteLineIndent("static constexpr size_t fbe_type() noexcept { return FinalModel<" + struct_name + ">::fbe_type(); }");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("bool verify();");

    // Generate struct model final serialize(), deserialize() methods
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("size_t serialize(const " + struct_name + "& value);");
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("size_t deserialize(" + struct_name + "& value) const noexcept;");

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("void next(size_t prev) noexcept { _model.fbe_shift(prev); }");

    // Generate struct model final accessor
    Indent(-1);
    WriteLine();
    WriteLineIndent("private:");
    Indent(1);
    WriteLineIndent("FinalModel<" + struct_name + "> _model;");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("};");

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
}

void GeneratorCpp::GenerateStructModelFinal_Source(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    // Generate namespace begin
    WriteLine();
    WriteLineIndent("namespace " + *p->name + " {");

    std::string struct_name = "::" + *p->name + "::" + *s->name;
    std::string model_name = *s->name + "FinalModel";

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("bool " + model_name + "::verify()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((this->buffer().offset() + _model.fbe_offset()) > this->buffer().size())");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("size_t fbe_struct_size = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 8));");
    WriteLineIndent("size_t fbe_struct_type = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 4));");
    WriteLineIndent("if ((fbe_struct_size == 0) || (fbe_struct_type != fbe_type()))");
    Indent(1);
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return ((8 + _model.verify()) == fbe_struct_size);");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model final serialize() method
    WriteLineIndent("size_t " + model_name + "::serialize(const " + struct_name + "& value)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("size_t fbe_initial_size = this->buffer().size();");
    WriteLine();
    WriteLineIndent("uint32_t fbe_struct_type = (uint32_t)fbe_type();");
    WriteLineIndent("uint32_t fbe_struct_size = (uint32_t)(8 + _model.fbe_allocation_size(value));");
    WriteLineIndent("uint32_t fbe_struct_offset = (uint32_t)(this->buffer().allocate(fbe_struct_size) - this->buffer().offset());");
    WriteLineIndent("assert(((this->buffer().offset() + fbe_struct_offset + fbe_struct_size) <= this->buffer().size()) && \"Model is broken!\");");
    WriteLineIndent("if ((this->buffer().offset() + fbe_struct_offset + fbe_struct_size) > this->buffer().size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = (uint32_t)(8 + _model.set(value));");
    WriteLineIndent("this->buffer().resize(fbe_initial_size + fbe_struct_size);");
    WriteLine();
    WriteLineIndent("*((uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 8)) = fbe_struct_size;");
    WriteLineIndent("*((uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 4)) = fbe_struct_type;");
    WriteLine();
    WriteLineIndent("return fbe_struct_size;");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate struct model final deserialize() method
    WriteLineIndent("size_t " + model_name + "::deserialize(" + struct_name + "& value) const noexcept");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("assert(((this->buffer().offset() + _model.fbe_offset()) <= this->buffer().size()) && \"Model is broken!\");");
    WriteLineIndent("if ((this->buffer().offset() + _model.fbe_offset()) > this->buffer().size())");
    Indent(1);
    WriteLineIndent("return 0;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("size_t fbe_struct_size = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 8));");
    WriteLineIndent("size_t fbe_struct_type = *((const uint32_t*)(this->buffer().data() + this->buffer().offset() + _model.fbe_offset() - 4));");
    WriteLineIndent("assert(((fbe_struct_size > 0) && (fbe_struct_type == fbe_type())) && \"Model is broken!\");");
    WriteLineIndent("if ((fbe_struct_size == 0) || (fbe_struct_type != fbe_type()))");
    Indent(1);
    WriteLineIndent("return 8;");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return 8 + _model.get(value);");
    Indent(-1);
    WriteLineIndent("}");

    // Generate namespace end
    WriteLine();
    WriteLineIndent("} // namespace " + *p->name);
}

void GeneratorCpp::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    // Generate protocol version struct
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *p->name + " protocol version");
    WriteLineIndent("struct ProtocolVersion");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Protocol major version");
    WriteLineIndent("static const int major = " + std::to_string(p->version->major) + ";");
    WriteLineIndent("// Protocol minor version");
    WriteLineIndent("static const int minor = " + std::to_string(p->version->minor) + ";");
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateSender_Header(const std::shared_ptr<Package>& p, bool final)
{
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate sender begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " final sender");
    else
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " sender");
    WriteLineIndent("class " + sender + " : public virtual FBE::Sender");
    if (p->import)
    {
        Indent(1);
        for (const auto& import : p->import->imports)
            WriteLineIndent(", public virtual " + *import + "::" + sender);
        Indent(-1);
    }
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate sender constructors
    WriteLineIndent(sender + "()");
    bool first = true;
    if (p->body)
    {
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent((first ? ": " : ", ") + *s->name + "" + "Model(this->_buffer)");
                first = false;
            }
        }
        Indent(-1);
    }
    WriteLineIndent("{" + std::string(final ? " this->final(true); " : "") + "}");
    WriteLineIndent(sender + "(const " + sender + "&) = delete;");
    WriteLineIndent(sender + "(" + sender + "&&) noexcept = delete;");
    WriteLineIndent("virtual ~" + sender + "() = default;");

    // Generate sender operators
    WriteLine();
    WriteLineIndent(sender + "& operator=(const " + sender + "&) = delete;");
    WriteLineIndent(sender + "& operator=(" + sender + "&&) noexcept = delete;");

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported senders");
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "::" + sender + "& " + *import + "_sender() noexcept { return *this; }");
    }

    // Generate send() methods
    if (p->body)
    {
        first = true;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                if (first)
                    WriteLine();
                WriteLineIndent("size_t send(const " + struct_name + "& value);");
                first = false;
            }
        }
    }

    // Generate sender models accessors
    if (p->body)
    {
        Indent(-1);
        WriteLine();
        WriteLineIndent("public:");
        Indent(1);
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("FBE::" + *p->name + "::" + *s->name + model + " " + *s->name + "Model;");
    }

    // Generate sender end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateSender_Source(const std::shared_ptr<Package>& p, bool final)
{
    std::string sender = (final ? "FinalSender" : "Sender");

    // Generate send() methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                WriteLine();
                WriteLineIndent("size_t " + sender + "::send(const " + struct_name + "& value)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("size_t serialized = " + *s->name + "Model.serialize(value);");
                WriteLineIndent("assert((serialized > 0) && \"" + *p->name + "::" + *s->name + " serialization failed!\");");
                WriteLineIndent("assert(" + *s->name + "Model.verify() && \"" + *p->name + "::" + *s->name + " validation failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this->_logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("std::string message = value.string();");
                WriteLineIndent("this->onSendLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return this->send_serialized(serialized);");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }
}

void GeneratorCpp::GenerateReceiver_Header(const std::shared_ptr<Package>& p, bool final)
{
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate receiver begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " final receiver");
    else
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " receiver");
    WriteLineIndent("class " + receiver + " : public virtual FBE::Receiver");
    if (p->import)
    {
        Indent(1);
        for (const auto& import : p->import->imports)
            WriteLineIndent(", public virtual " + *import + "::" + receiver);
        Indent(-1);
    }
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate receiver constructors
    WriteLineIndent(receiver + "() {" + std::string(final ? " this->final(true); " : "") + "}");
    WriteLineIndent(receiver + "(const " + receiver + "&) = delete;");
    WriteLineIndent(receiver + "(" + receiver + "&&) = delete;");
    WriteLineIndent("virtual ~" + receiver + "() = default;");

    // Generate receiver operators
    WriteLine();
    WriteLineIndent(receiver + "& operator=(const " + receiver + "&) = delete;");
    WriteLineIndent(receiver + "& operator=(" + receiver + "&&) = delete;");

    // Generate receiver handlers
    if (p->body)
    {
        WriteLine();
        Indent(-1);
        WriteLineIndent("protected:");
        Indent(1);
        WriteLineIndent("// Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                WriteLineIndent("virtual void onReceive(const " + struct_name + "& value) {}");
            }
        }
    }

    // Generate receiver message handler
    WriteLine();
    WriteLineIndent("// Receive message handler");
    WriteLineIndent("bool onReceive(size_t type, const void* data, size_t size) override;");

    // Generate receiver models accessors
    if (p->body)
    {
        Indent(-1);
        WriteLine();
        WriteLineIndent("private:");
        Indent(1);
        WriteLineIndent("// Receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                WriteLineIndent(struct_name + " " + *s->name + "Value;");
            }
        }
        WriteLine();
        WriteLineIndent("// Receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("FBE::" + *p->name + "::" + *s->name + model + " " + *s->name + "Model;");
    }

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateReceiver_Source(const std::shared_ptr<Package>& p, bool final)
{
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate receiver message handler
    WriteLine();
    WriteLineIndent("bool " + receiver + "::onReceive(size_t type, const void* data, size_t size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                WriteLineIndent("case FBE::" + *p->name + "::" + *s->name + model + "::fbe_type():");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "Model.attach(data, size);");
                WriteLineIndent("assert(" + *s->name + "Model.verify() && \"" + *p->name + "::" + *s->name + " validation failed!\");");
                WriteLineIndent("[[maybe_unused]] size_t deserialized = " + *s->name + "Model.deserialize(" + *s->name + "Value);");
                WriteLineIndent("assert((deserialized > 0) && \"" + *p->name + "::" + *s->name + " deserialization failed!\");");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (this->_logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("std::string message = " + *s->name + "Value.string();");
                WriteLineIndent("this->onReceiveLog(message);");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("onReceive(" + *s->name + "Value);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "::" + receiver + "::onReceive(type, data, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateProxy_Header(const std::shared_ptr<Package>& p, bool final)
{
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate proxy begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " final proxy");
    else
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " proxy");
    WriteLineIndent("class " + proxy + " : public virtual FBE::Receiver");
    if (p->import)
    {
        Indent(1);
        for (const auto& import : p->import->imports)
            WriteLineIndent(", public virtual " + *import + "::" + proxy);
        Indent(-1);
    }
    WriteLineIndent("{");
    WriteLineIndent("public:");
    Indent(1);

    // Generate proxy constructors
    WriteLineIndent(proxy + "() {" + std::string(final ? " this->final(true); " : "") + "}");
    WriteLineIndent(proxy + "(const " + proxy + "&) = delete;");
    WriteLineIndent(proxy + "(" + proxy + "&&) = delete;");
    WriteLineIndent("virtual ~" + proxy + "() = default;");

    // Generate proxy operators
    WriteLine();
    WriteLineIndent(proxy + "& operator=(const " + proxy + "&) = delete;");
    WriteLineIndent(proxy + "& operator=(" + proxy + "&&) = delete;");

    // Generate proxy handlers
    if (p->body)
    {
        WriteLine();
        Indent(-1);
        WriteLineIndent("protected:");
        Indent(1);
        WriteLineIndent("// Proxy handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_model = "FBE::" + *p->name + "::" + *s->name + model;
                WriteLineIndent("virtual void onProxy(" + struct_model + "& model, size_t type, const void* data, size_t size) {}");
            }
        }
    }

    // Generate proxy message handler
    WriteLine();
    WriteLineIndent("// Receive message handler");
    WriteLineIndent("bool onReceive(size_t type, const void* data, size_t size) override;");

    // Generate proxy models accessors
    if (p->body)
    {
        Indent(-1);
        WriteLine();
        WriteLineIndent("private:");
        Indent(1);
        WriteLineIndent("// Proxy models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("FBE::" + *p->name + "::" + *s->name + model + " " + *s->name + "Model;");
    }

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateProxy_Source(const std::shared_ptr<Package>& p, bool final)
{
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate proxy message handler
    WriteLine();
    WriteLineIndent("bool " + proxy + "::onReceive(size_t type, const void* data, size_t size)");
    WriteLineIndent("{");
    Indent(1);
    if (p->body)
    {
        WriteLineIndent("switch (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = "::" + *p->name + "::" + *s->name;
                WriteLineIndent("case FBE::" + *p->name + "::" + *s->name + model + "::fbe_type():");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Attach the FBE stream to the proxy model");
                WriteLineIndent(*s->name + "Model.attach(data, size);");
                WriteLineIndent("assert(" + *s->name + "Model.verify() && \"" + *p->name + "::" + *s->name + " validation failed!\");");
                WriteLine();
                WriteLineIndent("size_t fbe_begin = " + *s->name + "Model.model.get_begin();");
                WriteLineIndent("if (fbe_begin == 0)");
                Indent(1);
                WriteLineIndent("return false;");
                Indent(-1);
                WriteLineIndent("// Call proxy handler");
                WriteLineIndent("onProxy(" + *s->name + "Model, type, data, size);");
                WriteLineIndent(*s->name + "Model.model.get_end(fbe_begin);");
                WriteLineIndent("return true;");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("default: break;");
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "::" + proxy + "::onReceive(type, data, size))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false;");
    Indent(-1);
    WriteLineIndent("}");
}

void GeneratorCpp::GenerateClient_Header(const std::shared_ptr<Package>& p, bool final)
{
    std::string client = (final ? "FinalClient" : "Client");
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string receiver = (final ? "FinalReceiver" : "Receiver");

    // Generate client begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " final client");
    else
        WriteLineIndent("// Fast Binary Encoding " + *p->name + " client");
    WriteLineIndent("class " + client + " : public virtual " + sender + ", protected virtual " + receiver);
    if (p->import)
    {
        Indent(1);
        for (const auto& import : p->import->imports)
            WriteLineIndent(", public virtual " + *import + "::" + client);
        Indent(-1);
    }
    WriteLineIndent("{");
    WriteLineIndent("public:");
    if (p->import)
    {
        Indent(1);
        for (const auto& import : p->import->imports)
            WriteLineIndent("typedef " + *import + "::" + client + " " + *import + client + ";");
        Indent(-1);
        WriteLine();
    }
    Indent(1);

    // Generate client constructors
    WriteLineIndent(client + "() = default;");
    WriteLineIndent(client + "(const " + client + "&) = delete;");
    WriteLineIndent(client + "(" + client + "&&) = delete;");
    WriteLineIndent("virtual ~" + client + "() = default;");

    // Generate client operators
    WriteLine();
    WriteLineIndent(client + "& operator=(const " + client + "&) = delete;");
    WriteLineIndent(client + "& operator=(" + client + "&&) = delete;");

    // Generate imported clients accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("// Imported clients");
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "::" + client + "& " + *import + "_client() noexcept { return *this; }");
    }

    // Generate client reset method
    WriteLine();
    WriteLineIndent("// Reset client buffers");
    WriteLineIndent("void reset() { std::scoped_lock locker(this->_lock); reset_requests(); }");

    // Generate watchdog method
    WriteLine();
    WriteLineIndent("// Watchdog for timeouts");
    WriteLineIndent("void watchdog(uint64_t utc) { std::scoped_lock locker(this->_lock); watchdog_requests(utc); }");

    // Collect responses & rejects collections
    std::set<std::string> responses;
    std::map<std::string, bool> rejects;
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string response_name = (s->response) ? ConvertTypeName(*p->name, *s->response->response, false) : "";

                if (!response_name.empty())
                {
                    // Update responses and rejects cache
                    responses.insert(*s->response->response);
                    if (s->rejects)
                        for (const auto& reject : s->rejects->rejects)
                            rejects[*reject.reject] = reject.global;
                }
            }
        }
    }

    // Generate request() methods
    if (p->body)
    {
        WriteLine();
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string request_name = "::" + *p->name + "::" + *s->name;
                std::string response_name = (s->response) ? ConvertTypeName(*p->name, *s->response->response, false) : "";
                std::string response_field = (s->response) ? *s->response->response : "";
                CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

                if (response_name.empty())
                    WriteLineIndent("std::future<void> request(const " + request_name + "& value, uint64_t timeout = 0);");
                else
                    WriteLineIndent("std::future<" + response_name + "> request(const " + request_name + "& value, uint64_t timeout = 0);");
            }
        }
    }

    // Generate client protected fields
    Indent(-1);
    WriteLine();
    WriteLineIndent("protected:");
    Indent(1);
    if (!p->import)
    {
        WriteLineIndent("std::mutex _lock;");
        WriteLineIndent("uint64_t _timestamp{0};");
        WriteLine();
    }

    // Generate response handlers
    for (const auto& response : responses)
    {
        std::string response_name = ConvertTypeName(*p->name, response, false);
        WriteLineIndent("virtual bool onReceiveResponse(const " + response_name + "& response);");
    }
    if (!responses.empty())
        WriteLine();

    // Generate remaining response handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_response_name = ConvertTypeName(*p->name, *s->name, false);
                std::string struct_response_field = *s->name;

                if ((responses.find(*s->name) == responses.end()) && (cache.find(struct_response_name) == cache.end()))
                {
                    WriteLineIndent("virtual bool onReceiveResponse(const " + struct_response_name + "& response) { return false; }");
                    cache.insert(struct_response_name);
                    found = true;
                }
            }
        }
        if (found)
            WriteLine();
    }

    // Generate reject handlers
    for (const auto& reject : rejects)
    {
        std::string reject_name = ConvertTypeName(*p->name, reject.first, false);
        WriteLineIndent("virtual bool onReceiveReject(const " + reject_name + "& reject);");
    }
    if (!rejects.empty())
        WriteLine();

    // Generate remaining reject handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_reject_name = ConvertTypeName(*p->name, *s->name, false);
                if ((rejects.find(*s->name) == rejects.end()) && (cache.find(struct_reject_name) == cache.end()))
                {
                    WriteLineIndent("virtual bool onReceiveReject(const " + struct_reject_name + "& reject) { return false; }");
                    cache.insert(struct_reject_name);
                    found = true;
                }
            }
        }
        if (found)
            WriteLine();
    }

    // Generate notify handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_notify_name = ConvertTypeName(*p->name, *s->name, false);
                std::string struct_notify_field = *s->name;
                if (cache.find(struct_notify_name) == cache.end())
                {
                    WriteLineIndent("virtual void onReceiveNotify(const " + struct_notify_name + "& notify) {}");
                    cache.insert(struct_notify_name);
                    found = true;
                }
            }
        }
        if (found)
            WriteLine();
    }

    // Generate receive handlers
    if (p->body)
    {
        bool found = false;
        std::set<std::string> cache;
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_response_name = ConvertTypeName(*p->name, *s->name, false);
                std::string struct_response_field = *s->name;

                if (cache.find(struct_response_name) == cache.end())
                {
                    WriteLineIndent("virtual void onReceive(const " + struct_response_name + "& value) override { if (!onReceiveResponse(value) && !onReceiveReject(value)) onReceiveNotify(value); }");
                    cache.insert(struct_response_name);
                    found = true;
                }
            }
        }
        if (found)
            WriteLine();
    }

    // Generate reset requests method
    WriteLineIndent("// Reset client requests");
    WriteLineIndent("virtual void reset_requests();");

    // Generate watchdog requests method
    WriteLine();
    WriteLineIndent("// Watchdog client requests for timeouts");
    WriteLineIndent("virtual void watchdog_requests(uint64_t utc);");

    // Generate client private fields
    if (!responses.empty())
    {
        Indent(-1);
        WriteLine();
        WriteLineIndent("private:");
        Indent(1);
        for (const auto& response : responses)
        {
            std::string response_name = ConvertTypeName(*p->name, response, false);
            std::string response_field = response;
            CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

            WriteLineIndent("std::unordered_map<FBE::uuid_t, std::tuple<uint64_t, uint64_t, std::promise<" + response_name + ">>> _requests_by_id_" + response_field + ";");
            WriteLineIndent("std::map<uint64_t, FBE::uuid_t> _requests_by_timestamp_" + response_field + ";");
        }
    }

    // Generate client end
    Indent(-1);
    WriteLineIndent("};");
}

void GeneratorCpp::GenerateClient_Source(const std::shared_ptr<Package>& p, bool final)
{
    std::string client = (final ? "FinalClient" : "Client");
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string receiver = (final ? "FinalReceiver" : "Receiver");

    // Collect responses & rejects collections
    std::set<std::string> responses;
    std::map<std::string, bool> rejects;
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string response_name = (s->response) ? ConvertTypeName(*p->name, *s->response->response, false) : "";

                if (!response_name.empty())
                {
                    // Update responses and rejects cache
                    responses.insert(*s->response->response);
                    if (s->rejects)
                        for (const auto& reject : s->rejects->rejects)
                            rejects[*reject.reject] = reject.global;
                }
            }
        }
    }

    // Generate request() methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message && s->request)
            {
                std::string request_name = "::" + *p->name + "::" + *s->name;
                std::string response_name = (s->response) ? ConvertTypeName(*p->name, *s->response->response, false) : "";
                std::string response_field = (s->response) ? *s->response->response : "";
                CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

                WriteLine();
                if (response_name.empty())
                {
                    WriteLineIndent("std::future<void> " + client + "::request(const " + request_name + "& value, uint64_t timeout)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("std::promise<void> promise;");
                    WriteLineIndent("std::future<void> future = promise.get_future();");
                    WriteLine();
                    WriteLineIndent("// Send the request message");
                    WriteLineIndent("size_t serialized = " + sender + "::send(value);");
                    WriteLineIndent("if (serialized > 0)");
                    Indent(1);
                    WriteLineIndent("promise.set_value();");
                    Indent(-1);
                    WriteLineIndent("else");
                    Indent(1);
                    WriteLineIndent("promise.set_exception(std::make_exception_ptr(std::runtime_error(\"Send request failed!\")));");
                    Indent(-1);
                    WriteLine();
                    WriteLineIndent("return future;");
                    Indent(-1);
                    WriteLineIndent("}");
                }
                else
                {
                    WriteLineIndent("std::future<" + response_name + "> " + client + "::request(const " + request_name + "& value, uint64_t timeout)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("std::scoped_lock locker(this->_lock);");
                    WriteLine();
                    WriteLineIndent("std::promise<" + response_name + "> promise;");
                    WriteLineIndent("std::future<" + response_name + "> future = promise.get_future();");
                    WriteLine();
                    WriteLineIndent("uint64_t current = utc();");
                    WriteLine();
                    WriteLineIndent("// Send the request message");
                    WriteLineIndent("size_t serialized = " + sender + "::send(value);");
                    WriteLineIndent("if (serialized > 0)");
                    WriteLineIndent("{");
                    Indent(1);
                    WriteLineIndent("// Calculate the unique timestamp");
                    WriteLineIndent("this->_timestamp = (current <= this->_timestamp) ? this->_timestamp + 1 : current;");
                    WriteLine();
                    WriteLineIndent("// Register the request");
                    WriteLineIndent("_requests_by_id_" + response_field + ".insert(std::make_pair(value.id, std::make_tuple(this->_timestamp, timeout * 1000000, std::move(promise))));");
                    WriteLineIndent("if (timeout > 0)");
                    Indent(1);
                    WriteLineIndent("_requests_by_timestamp_" + response_field + ".insert(std::make_pair(this->_timestamp, value.id));");
                    Indent(-1);
                    Indent(-1);
                    WriteLineIndent("}");
                    WriteLineIndent("else");
                    Indent(1);
                    WriteLineIndent("promise.set_exception(std::make_exception_ptr(std::runtime_error(\"Send request failed!\")));");
                    Indent(-1);
                    WriteLine();
                    WriteLineIndent("return future;");
                    Indent(-1);
                    WriteLineIndent("}");
                }
            }
        }
    }

    // Generate response handlers
    for (const auto& response : responses)
    {
        std::string response_name = ConvertTypeName(*p->name, response, false);
        std::string response_field = response;

        WriteLine();
        WriteLineIndent("bool " + client + "::onReceiveResponse(const " + response_name + "& response)");
        WriteLineIndent("{");
        Indent(1);
        if (p->body)
        {
            std::set<std::string> cache;
            WriteLineIndent("std::scoped_lock locker(this->_lock);");
            WriteLine();
            for (const auto& s : p->body->structs)
            {
                if (s->message && s->response)
                {
                    std::string struct_response_name = ConvertTypeName(*p->name, *s->response->response, false);
                    std::string struct_response_field = *s->response->response;
                    CppCommon::StringUtils::ReplaceAll(struct_response_field, ".", "");

                    if ((struct_response_name == response_name) && (cache.find(struct_response_name) == cache.end()))
                    {
                        WriteLineIndent("auto it_" + struct_response_field + " = _requests_by_id_" + struct_response_field + ".find(response.id);");
                        WriteLineIndent("if (it_" + struct_response_field + " != _requests_by_id_" + struct_response_field + ".end())");
                        WriteLineIndent("{");
                        Indent(1);
                        WriteLineIndent("auto timestamp = std::get<0>(it_" + struct_response_field + "->second);");
                        WriteLineIndent("[[maybe_unused]] auto timespan = std::get<1>(it_" + struct_response_field + "->second);");
                        WriteLineIndent("auto& promise = std::get<2>(it_" + struct_response_field + "->second);");
                        WriteLineIndent("promise.set_value(response);");
                        WriteLineIndent("_requests_by_id_" + struct_response_field + ".erase(response.id);");
                        WriteLineIndent("_requests_by_timestamp_" + struct_response_field + ".erase(timestamp);");
                        WriteLineIndent("return true;");
                        cache.insert(struct_response_name);
                        Indent(-1);
                        WriteLineIndent("}");
                        WriteLine();
                    }
                }
            }
        }
        WriteLineIndent("return false;");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate reject handlers
    for (const auto& reject : rejects)
    {
        std::string reject_name = ConvertTypeName(*p->name, reject.first, false);
        std::string reject_field = reject.first;
        bool global = reject.second;
        bool imported = CppCommon::StringUtils::ReplaceAll(reject_field, ".", "");

        WriteLine();
        WriteLineIndent("bool " + client + "::onReceiveReject(const " + reject_name + "& reject)");
        WriteLineIndent("{");
        Indent(1);
        if (global)
        {
            if (p->import)
            {
                for (const auto& import : p->import->imports)
                {
                    WriteLineIndent("if (" + *import + client + "::onReceiveReject(reject))");
                    Indent(1);
                    WriteLineIndent("return true;");
                    Indent(-1);
                }
                WriteLine();
            }
        }
        else if (imported)
        {
            std::string ns = "";
            std::string t = reject.first;
            std::string type = reject.first;

            size_t pos = type.find_last_of('.');
            if (pos != std::string::npos)
            {
                ns.assign(type, 0, pos);
                t.assign(type, pos + 1, type.size() - pos);
            }

            WriteLineIndent("if (" + ns + client + "::onReceiveReject(reject))");
            Indent(1);
            WriteLineIndent("return true;");
            Indent(-1);
            WriteLine();
        }
        if (p->body)
        {
            std::set<std::string> cache;
            WriteLineIndent("std::scoped_lock locker(this->_lock);");
            WriteLine();
            for (const auto& s : p->body->structs)
            {
                if (s->message && s->response && s->rejects)
                {
                    for (const auto& r : s->rejects->rejects)
                    {
                        std::string struct_response_name = ConvertTypeName(*p->name, *s->response->response, false);
                        std::string struct_response_field = *s->response->response;
                        CppCommon::StringUtils::ReplaceAll(struct_response_field, ".", "");

                        std::string struct_reject_name = ConvertTypeName(*p->name, *r.reject, false);
                        std::string struct_reject_field = *r.reject;
                        CppCommon::StringUtils::ReplaceAll(struct_reject_field, ".", "");

                        if ((struct_reject_name == reject_name) && (cache.find(struct_response_field) == cache.end()))
                        {
                            WriteLineIndent("auto it_" + struct_response_field + " = _requests_by_id_" + struct_response_field + ".find(reject.id);");
                            WriteLineIndent("if (it_" + struct_response_field + " != _requests_by_id_" + struct_response_field + ".end())");
                            WriteLineIndent("{");
                            Indent(1);
                            WriteLineIndent("auto timestamp = std::get<0>(it_" + struct_response_field + "->second);");
                            WriteLineIndent("[[maybe_unused]] auto timespan = std::get<1>(it_" + struct_response_field + "->second);");
                            WriteLineIndent("auto& promise = std::get<2>(it_" + struct_response_field + "->second);");
                            WriteLineIndent("promise.set_exception(std::make_exception_ptr(std::runtime_error(reject.string())));");
                            WriteLineIndent("_requests_by_id_" + struct_response_field + ".erase(reject.id);");
                            WriteLineIndent("_requests_by_timestamp_" + struct_response_field + ".erase(timestamp);");
                            WriteLineIndent("return true;");
                            cache.insert(struct_response_field);
                            Indent(-1);
                            WriteLineIndent("}");
                            WriteLine();
                        }
                    }
                }
            }
        }
        WriteLineIndent("return false;");
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate reset requests method
    WriteLine();
    WriteLineIndent("void " + client + "::reset_requests()");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "::" + client + "::reset_requests();");
    }
    else
    {
        WriteLineIndent(sender + "::reset();");
        WriteLineIndent(receiver + "::reset();");
    }
    for (const auto& response : responses)
    {
        std::string response_name = ConvertTypeName(*p->name, response, false);
        std::string response_field = response;
        CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

        WriteLine();
        WriteLineIndent("for (auto& request : _requests_by_id_" + response_field + ")");
        Indent(1);
        WriteLineIndent("std::get<2>(request.second).set_exception(std::make_exception_ptr(std::runtime_error(\"Reset client!\")));");
        Indent(-1);
        WriteLineIndent("_requests_by_id_" + response_field + ".clear();");
        WriteLineIndent("_requests_by_timestamp_" + response_field + ".clear();");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate watchdog requests method
    WriteLine();
    WriteLineIndent("void " + client + "::watchdog_requests(uint64_t utc)");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "::" + client + "::watchdog_requests(utc);");
        WriteLine();
    }
    for (const auto& response : responses)
    {
        std::string response_name = ConvertTypeName(*p->name, response, false);
        std::string response_field = response;
        CppCommon::StringUtils::ReplaceAll(response_field, ".", "");

        WriteLineIndent("auto it_request_by_timestamp_" + response_field + " = _requests_by_timestamp_" + response_field + ".begin();");
        WriteLineIndent("while (it_request_by_timestamp_" + response_field + " != _requests_by_timestamp_" + response_field + ".end())");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("auto& it_request_by_id_" + response_field + " = _requests_by_id_" + response_field + "[it_request_by_timestamp_" + response_field + "->second];");
        WriteLineIndent("auto id = it_request_by_timestamp_" + response_field + "->second;");
        WriteLineIndent("auto timestamp = std::get<0>(it_request_by_id_" + response_field + ");");
        WriteLineIndent("auto timespan = std::get<1>(it_request_by_id_" + response_field + ");");
        WriteLineIndent("if ((timestamp + timespan) <= utc)");
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("auto& promise = std::get<2>(it_request_by_id_" + response_field + ");");
        WriteLineIndent("promise.set_exception(std::make_exception_ptr(std::runtime_error(\"Timeout!\")));");
        WriteLineIndent("_requests_by_id_" + response_field + ".erase(id);");
        WriteLineIndent("_requests_by_timestamp_" + response_field + ".erase(timestamp);");
        WriteLineIndent("it_request_by_timestamp_" + response_field + " = _requests_by_timestamp_" + response_field + ".begin();");
        WriteLineIndent("continue;");
        Indent(-1);
        WriteLineIndent("}");
        WriteLineIndent("else");
        Indent(1);
        WriteLineIndent("break;");
        Indent(-1);
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }
    Indent(-1);
    WriteLineIndent("}");
}

bool GeneratorCpp::IsKnownType(const std::string& type)
{
    return ((type == "bool") ||
            (type == "byte") || (type == "bytes") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "decimal") || (type == "string") ||
            (type == "timestamp") || (type == "uuid"));
}

bool GeneratorCpp::IsPrimitiveType(const std::string& type, bool optional)
{
    if (optional)
        return false;

    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "timestamp"));
}

std::string GeneratorCpp::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "uint8_t";
    else if (type == "char")
        return "uint8_t";
    else if (type == "wchar")
        return "uint32_t";
    else if (type == "int8")
        return "int8_t";
    else if (type == "uint8")
        return "uint8_t";
    else if (type == "int16")
        return "int16_t";
    else if (type == "uint16")
        return "uint16_t";
    else if (type == "int32")
        return "int32_t";
    else if (type == "uint32")
        return "uint32_t";
    else if (type == "int64")
        return "int64_t";
    else if (type == "uint64")
        return "uint64_t";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorCpp::ConvertTypeName(const std::string& package, const std::string& type, bool optional)
{
    if (optional)
        return "std::optional<" + ConvertTypeName(package, type, false) + ">";

    if (type == "bool")
        return "bool";
    else if (type == "byte")
        return "uint8_t";
    else if (type == "bytes")
        return "FBE::buffer_t";
    else if (type == "char")
        return "char";
    else if (type == "wchar")
        return "wchar_t";
    else if (type == "int8")
        return "int8_t";
    else if (type == "uint8")
        return "uint8_t";
    else if (type == "int16")
        return "int16_t";
    else if (type == "uint16")
        return "uint16_t";
    else if (type == "int32")
        return "int32_t";
    else if (type == "uint32")
        return "uint32_t";
    else if (type == "int64")
        return "int64_t";
    else if (type == "uint64")
        return "uint64_t";
    else if (type == "float")
        return "float";
    else if (type == "double")
        return "double";
    else if (type == "decimal")
        return "FBE::decimal_t";
    else if (type == "string")
        return "std::string";
    else if (type == "timestamp")
        return "uint64_t";
    else if (type == "uuid")
        return "FBE::uuid_t";

    std::string result = type;
    bool pkg = !CppCommon::StringUtils::ReplaceAll(result, ".", "::");
    return (pkg ? ("::" + package) : "") + "::" + result;
}

std::string GeneratorCpp::ConvertTypeName(const std::string& package, const StructField& field)
{
    if (field.array)
        return "std::array<" + ConvertTypeName(package, *field.type, field.optional) + ", " + std::to_string(field.N) + ">";
    else if (field.vector)
        return "std::vector<" + ConvertTypeName(package, *field.type, field.optional) + ">";
    else if (field.list)
        return "std::list<" + ConvertTypeName(package, *field.type, field.optional) + ">";
    else if (field.set)
        return "std::set<" + ConvertTypeName(package, *field.key, false) + ">";
    else if (field.map)
        return "std::map<" + ConvertTypeName(package, *field.key, false) + ", " + ConvertTypeName(package, *field.type, field.optional) +">";
    else if (field.hash)
        return "std::unordered_map<" + ConvertTypeName(package, *field.key, false) + ", " + ConvertTypeName(package, *field.type, field.optional) +">";

    return ConvertTypeName(package, *field.type, field.optional);
}

std::string GeneratorCpp::ConvertTypeNameAsArgument(const std::string& package, const StructField& field)
{
    if (field.optional || field.array || field.vector || field.list || field.set || field.map || field.hash)
        return "const " + ConvertTypeName(package, field) + "&";

    if (IsPrimitiveType(*field.type, false))
        return ConvertTypeName(package, field);

    return "const " + ConvertTypeName(package, field) + "&";
}

std::string GeneratorCpp::ConvertConstant(const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return optional ? "std::nullopt" : "std::nullptr";
    else if (value == "min")
    {
        if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32") || (type == "uint64"))
            return ConvertConstantPrefix(type) + "0" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return ConvertConstantPrefix(type) + "-128" + ConvertConstantSuffix(type);
        else if (type == "int16")
            return ConvertConstantPrefix(type) + "-32768" + ConvertConstantSuffix(type);
        else if (type == "int32")
            return ConvertConstantPrefix(type) + "-2147483648ll" + ConvertConstantSuffix(type);
        else if (type == "int64")
            return ConvertConstantPrefix(type) + "-9223372036854775808ll" + ConvertConstantSuffix(type);

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return ConvertConstantPrefix(type) + "255" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return ConvertConstantPrefix(type) + "127" + ConvertConstantSuffix(type);
        else if (type == "uint8")
            return ConvertConstantPrefix(type) + "255" + ConvertConstantSuffix(type);
        else if (type == "int16")
            return ConvertConstantPrefix(type) + "32767" + ConvertConstantSuffix(type);
        else if (type == "uint16")
            return ConvertConstantPrefix(type) + "65535" + ConvertConstantSuffix(type);
        else if (type == "int32")
            return ConvertConstantPrefix(type) + "2147483647" + ConvertConstantSuffix(type);
        else if (type == "uint32")
            return ConvertConstantPrefix(type) + "4294967295" + ConvertConstantSuffix(type);
        else if (type == "int64")
            return ConvertConstantPrefix(type) + "9223372036854775807" + ConvertConstantSuffix(type);
        else if (type == "uint64")
            return ConvertConstantPrefix(type) + "18446744073709551615" + ConvertConstantSuffix(type);

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return "FBE::epoch()";
    else if (value == "utc")
        return "FBE::utc()";
    else if (value == "uuid0")
        return "FBE::uuid_t::nil()";
    else if (value == "uuid1")
        return "FBE::uuid_t::sequential()";
    else if (value == "uuid4")
        return "FBE::uuid_t::random()";

    if (IsKnownType(type))
        return ConvertConstantPrefix(type) + value + ConvertConstantSuffix(type);

    bool first = true;
    std::string result;
    auto items = CppCommon::StringUtils::Split(value, "|");
    for (auto& item : items)
    {
        bool pkg = (CppCommon::StringUtils::CountAll(item, ".") > 1);
        CppCommon::StringUtils::ReplaceAll(item, ".", "::");
        result += (!first ? " | " : "");
        result += (pkg ? "::" : "") + item;
        first = false;
    }

    return result;
}

std::string GeneratorCpp::ConvertConstantPrefix(const std::string& type)
{
    if (type == "bool")
        return "(bool)";
    else if (type == "byte")
        return "(uint8_t)";
    else if (type == "char")
        return "(char)";
    else if (type == "wchar")
        return "(wchar_t)";
    else if (type == "int8")
        return "(int8_t)";
    else if (type == "uint8")
        return "(uint8_t)";
    else if (type == "int16")
        return "(int16_t)";
    else if (type == "uint16")
        return "(uint16_t)";
    else if (type == "int32")
        return "(int32_t)";
    else if (type == "uint32")
        return "(uint32_t)";
    else if (type == "int64")
        return "(int64_t)";
    else if (type == "uint64")
        return "(uint64_t)";
    else if (type == "float")
        return "(float)";
    else if (type == "double")
        return "(double)";
    else if (type == "decimal")
        return "FBE::decimal_t(";
    else if (type == "timestamp")
        return "(uint64_t)";
    else if (type == "uuid")
        return "FBE::uuid_t(";

    return "";
}

std::string GeneratorCpp::ConvertConstantSuffix(const std::string& type)
{
    if ((type == "byte") || (type == "uint8") || (type == "uint16"))
        return "u";
    else if ((type == "int32") || (type == "int64"))
        return "ll";
    else if ((type == "uint32") || (type == "uint64") || (type == "timestamp"))
        return "ull";
    else if (type == "float")
        return "f";
    else if ((type == "decimal") || (type == "uuid"))
        return ")";

    return "";
}

std::string GeneratorCpp::ConvertDefault(const std::string& package, const std::string& type)
{
    if (type == "bool")
        return "false";
    else if (type == "bytes")
        return "";
    else if (type == "char")
        return "'\\0'";
    else if (type == "wchar")
        return "L'\\0'";
    else if ((type == "byte") || (type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64") || (type == "timestamp"))
        return ConvertConstantPrefix(type) + "0" + ConvertConstantSuffix(type);
    else if (type == "float")
        return "0.0f";
    else if (type == "double")
        return "0.0";
    else if (type == "decimal")
        return "FBE::decimal_t()";
    else if (type == "string")
        return "\"\"";
    else if (type == "uuid")
        return "FBE::uuid_t::nil()";

    std::string result = type + "()";
    bool pkg = !CppCommon::StringUtils::ReplaceAll(result, ".", "::");
    return (pkg ? ("::" + package) : "") + "::" + result;
}

std::string GeneratorCpp::ConvertDefault(const std::string& package, const StructField& field)
{
    if (field.value)
        return ConvertConstant(*field.type, *field.value, field.optional);

    if (field.array || field.vector || field.list || field.set || field.map || field.hash)
        return "";
    else if (field.optional)
        return "std::nullopt";

    return ConvertDefault(package, *field.type);
}

std::string GeneratorCpp::ConvertOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        return "(" + std::string(optional ? "*" : "") + name + " ? \"true\" : \"false\"" + ")";
    else if ((type == "byte") || (type == "int8") || (type == "uint8"))
        return "(int)" + std::string(optional ? "*" : "") + name;
    else if (type == "bytes")
        return "\"bytes[\" << " + name + std::string(optional ? "->" : ".") + "size() << \"]\"";
    else if (type == "char")
        return "\"'\" << " + std::string(optional ? "*" : "") + name + " << \"'\"";
    else if (type == "wchar")
        return "\"'\" << (char)" + std::string(optional ? "*" : "") + name + " << \"'\"";
    else if ((type == "string") || (type == "uuid"))
        return "\"\\\"\" << " + std::string(optional ? "*" : "") + name + " << \"\\\"\"";
    else
        return std::string(optional ? "*" : "") + name;
}

std::string GeneratorCpp::ConvertOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    std::string comma = separate ? "<< std::string(first ? \"\" : \",\") " : "";

    if (optional)
        return "if (" + name + ") stream " + comma + "<< " + ConvertOutputStreamType(type, name, true) + "; else stream " + comma + "<< \"null\";";
    else
        return "stream " + comma + "<< " + ConvertOutputStreamType(type, name, false) + ";";
}

std::string GeneratorCpp::ConvertLoggingStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        return "StoreList(" + std::string(optional ? "*" : "") + name + " ? \"true\" : \"false\"" + ")";
    else if ((type == "byte") || (type == "int8") || (type == "uint8"))
        return "StoreList((int)" + std::string(optional ? "*" : "") + name + ")";
    else if (type == "bytes")
        return "StoreList(\"bytes[\").StoreList(" + name + std::string(optional ? "->" : ".") + "size()).StoreList(\"]\")";
    else if ((type == "char") || (type == "wchar"))
        return "StoreList(\"'\").StoreList(" + std::string(optional ? "*" : "") + name + ").StoreList(\"'\")";
    else if ((type == "string") || (type == "uuid"))
        return "StoreList(\"\\\"\").StoreList(" + std::string(optional ? "*" : "") + name + ").StoreList(\"\\\"\")";
    else
        return "StoreList(" + std::string(optional ? "*" : "") + name + ")";
}

std::string GeneratorCpp::ConvertLoggingStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    std::string comma = separate ? "StoreList(first ? \"\" : \",\")." : "";

    if (optional)
        return "if (" + name + ") record." + comma + ConvertLoggingStreamType(type, name, true) + "; else record." + comma + "StoreList(\"null\");";
    else
        return "record." + comma + ConvertLoggingStreamType(type, name, false) + ";";
}

} // namespace FBE
